call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

  1. fun.call(thisArg[, arg1[, arg2[, ...]]])

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

  1. fun.apply(thisArg, [argsArray])

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数

  1. fun.bind(thisArg[, arg1[, arg2[, ...]]])

当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。

特点:

返回一个函数

可以传入参数

1、理解

call方法原理

  1. 模拟Function中内置的call方法,写一个myCall方法,探讨call方法的执行原理
  2. function sum(){
  3. console.log(this);
  4. }
  5. function fn(){
  6. console.log(this);
  7. }
  8. var obj = {name:'iceman'};
  9. Function.prototype.myCall = function (context) {
  10. // myCall方法中的this就是当前我要操作和改变其this关键字的那个函数名
  11. // 1、让fn中的this关键字变为context的值->obj
  12. // 让this这个函数中的"this关键字"变为context
  13. // eval(this.toString().replace("this","obj"));
  14. // 2、让fn方法在执行
  15. // this();
  16. };
  17. fn.myCall(obj);// myCall方法中原来的this是fn
  18. sum.myCall(obj);// myCall方法中原来的this是sum

call方法经典例子

  1. function fn1() {
  2. console.log(1);
  3. }
  4. function fn2() {
  5. console.log(2);
  6. }
  1. fn1.call(fn2); // 1

首先fn1通过原型链查找机制找到Function.prototype上的call方法,并且让call方法执行,此时call这个方法中的this就是要操作的fn1。在call方法代码执行的过程过程中,首先让fn1中的“this关键字”变为fn2,然后再让fn1这个方法执行。

  1. fn1.call.call(fn2); // 2

2、区别

三个函数存在的区别, 用一句话来说的话就是: bind是返回对应函数, 便于稍后调用; apply, call则是立即调用,apply是call的一层封装,可以传数组。所以call比较快。 除此外, 在 ES6 的箭头函数下, call 和 apply 的失效, 对于箭头函数来说:

  • 函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;
  • 不可以当作构造函数, 也就是说不可以使用 new 命令, 否则会抛出一个错误;
  • 不可以使用 arguments 对象, 该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替;
  • 不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数;

call 方法比 apply 快的原因是 call 方法的参数格式正是内部方法所需要的格式

3、模拟

思路
  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数

call

  1. Function.prototype.call2 = function (context) {
  2. var context = context || window;//null等传入指向window
  3. context.fn = this;//将函数设为对象的属性
  4. var args = [];
  5. for(var i = 1, len = arguments.length; i < len; i++) {
  6. args.push('arguments[' + i + ']');
  7. }
  8. var result = eval('context.fn(' + args +')');//执行该函数
  9. delete context.fn; // 删除函数
  10. return result;
  11. }

apply

  1. Function.prototype.apply = function (context, arr) {
  2. var context = Object(context) || window;
  3. context.fn = this;
  4. var result;
  5. if (!arr) {
  6. result = context.fn();
  7. }
  8. else {
  9. var args = [];
  10. for (var i = 0, len = arr.length; i < len; i++) {
  11. args.push('arr[' + i + ']');
  12. }
  13. result = eval('context.fn(' + args + ')')
  14. }
  15. delete context.fn
  16. return result;
  17. }

bind

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

Bound.prototype = this.prototype,我们直接修改 Bound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转

  1. Function.prototype.bind = Function.prototype.bind || function (context) {
  2. if (typeof this !== "function") {
  3. throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  4. }
  5. var self = this;
  6. var args = Array.prototype.slice.call(arguments, 1);//先取到bind时传参,bar.bind(foo, 'daisy');
  7. var F = function () {};//继承用空函数来转
  8. var Bound = function () {
  9. var bindArgs = Array.prototype.slice.call(arguments);//在取到返回函数,bindFoo('18');
  10. //如果是new的this指向F,this指向构造函数,如果不是指向要改变的this
  11. return self.apply(this instanceof F ? this : context, args.concat(bindArgs)//合并参数);
  12. }
  13. F.prototype = this.prototype;
  14. Bound.prototype = new F();
  15. return Bound;
  16. };

4、案例

1、柯里化

  1. var currying = function( fn ){
  2. var args = [];
  3. return function(){
  4. if ( arguments.length === 0 ){
  5. return fn.apply( this, args );
  6. }else{
  7. [].push.apply( args, arguments );
  8. return arguments.callee;
  9. }
  10. }
  11. };
  12. var cost = (function(){
  13. var money = 0;
  14. return function(){
  15. for ( var i = 0, l = arguments.length; i < l; i++ ){
  16. money += arguments[ i ];
  17. }
  18. return money;
  19. }
  20. })();
  21. var cost = currying( cost ); // 转化成 currying 函数
  22. cost( 100 ); // 未真正求值
  23. cost( 200 ); // 未真正求值
  24. cost( 300 ); // 未真正求值
  25. alert ( cost() ); // 求值并输出:
  26. var overtime = (function() {
  27. var args = [];
  28. return function() {
  29. if(arguments.length === 0) {
  30. var time = 0;
  31. for (var i = 0, l = args.length; i < l; i++) {
  32. time += args[i];
  33. }
  34. return time;
  35. }else {
  36. [].push.apply(args, arguments);
  37. }
  38. }
  39. })();
  40. overtime(3.5); // 第一天
  41. overtime(4.5); // 第二天
  42. overtime(2.1); // 第三天
  43. //...
  44. console.log( overtime() ); // 10.1

2、debounce 函数去抖

  1. var debounce = function(idle, action){
  2. var last
  3. return function(){
  4. var ctx = this, args = arguments
  5. clearTimeout(last)
  6. last = setTimeout(function(){
  7. action.apply(ctx, args)
  8. }, idle)
  9. }
  10. }
  11. var timer = null;
  12. window.onscroll = function(){
  13. if (timer) {
  14. // 清除未执行的逻辑,重新执行下一次逻辑,不论上一次是否执行完毕
  15. clearTimeout(timer);
  16. }
  17. timer = setTimeout(function(){
  18. //执行逻辑
  19. }, 300);
  20. };

3、throttle 函数节流

  1. var throttle = function ( fn, interval ) {
  2. var __self = fn, // 保存需要被延迟执行的函数引用
  3. timer, // 定时器
  4. firstTime = true; // 是否是第一次调用
  5. return function () {
  6. var args = arguments,
  7. __me = this;
  8. if ( firstTime ) { // 如果是第一次调用,不需延迟执行
  9. __self.apply(__me, args);
  10. return firstTime = false;
  11. }
  12. if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
  13. return false;
  14. }
  15. timer = setTimeout(function () { // 延迟一段时间执行
  16. clearTimeout(timer);
  17. timer = null;
  18. __self.apply(__me, args);
  19. }, interval || 5000 );
  20. };
  21. };
  22. window.onresize = throttle(function(){ console.log( 1 ); }, 5000 );
  23. var can = true;
  24. window.onscroll = function(){
  25. if(!can){
  26. //判断上次逻辑是否执行完毕,如果在执行中,则直接return
  27. return;
  28. }
  29. can = false;
  30. setTimeout(function(){
  31. //执行逻辑
  32. can = true;
  33. }, 100);
  34. };

4、反柯里化(uncurring)

  1. Function.prototype.uncurring = function() {
  2. var self = this; //self此时是Array.prototype.push
  3. return function() {
  4. var obj = Array.prototype.shift.call(arguments);
  5. //obj 是{
  6. // "length": 1,
  7. // "0": 1
  8. //}
  9. //arguments的第一个对象被截去(也就是调用push方法的对象),剩下[2]
  10. return self.apply(obj, arguments);
  11. //相当于Array.prototype.push.apply(obj, 2);
  12. };
  13. };
  14. //测试一下
  15. var push = Array.prototype.push.uncurring();
  16. var obj = {
  17. "length": 1,
  18. "0" : 1
  19. };
  20. push(obj, 2);
  21. console.log( obj ); //{0: 1,1: 2, length: 2 }

5、取数组最大值

Math.max(1,2,3,4);

利用apply可以把传数组的方法

  1. var max = Math.max.apply(null, ary);
  2. Math.max(1,2,3,4);
  3. var max = eval("Math.max(" + ary.toString() + ")");

在非严格模式下,给apply的第一个参数为null的时候,会让max/min中的this指向window,然后将ary的参数一个个传给max/min

6、将类数组转换数组

slice在不穿参的情况下是复制数组

  1. Array.prototype.slice = function() {
  2. var result = [];
  3. for(var i = 0; i < this.length; i++){
  4. result[i] = this[i] };
  5. return result;
  6. }
  7. [1,2,3,4].slice()
  8. => [1, 2, 3, 4]
  9. var obj = {length:'2', '0': 'aa', '1': 'bb'}
  10. [].slice.call(obj)
  11. => ["aa", "bb"]
  1. function listToArray(likeAry) {
  2. var ary = [];
  3. try {
  4. ary = Array.prototype.slice.call(likeAry);
  5. } catch (e) {
  6. for (var i = 0; i < likeAry.length; i++) {
  7. ary[ary.length] = likeAry[i];
  8. }
  9. }
  10. return ary;
  11. }

7、push的理解

  1. Array.prototype.push = function(str){ return this.concat(str) }
  2. [1,2,3].push(1)
  3. =>[1, 2, 3, 1]
  4. [].push.call([1,2,3],3)
  5. => [1, 2, 3, 3]

5、caller与callee

caller

返回一个对函数的引用,该函数调用了当前函数。

对于函数来说,caller 属性只有在函数执行时才有定义。

如果函数是由顶层调用的,那么 caller 包含的就是 null 。

如果在字符串上下文中使用 caller 属性,那么结果和 functionName.toString 一样,也就是说,显示的是函数的反编译文本。

  1. // caller demo {
  2. function callerDemo() {
  3. if (callerDemo.caller) {
  4. var a= callerDemo.caller.toString();
  5. alert(a);
  6. } else {
  7. alert("this is a top function");
  8. }
  9. }
  10. function handleCaller() {
  11. callerDemo(); //"function handleCaller() { callerDemo();}"
  12. }

callee

返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。

ES5 提示: 在严格模式下,arguments.callee 会报错 TypeError,因为它已经被废除了

callee 属性是 arguments 对象的一个成员,它表示对函数对象本身的引用,这有利于匿名函数的递归或者保证函数的封装性

arguments.length是实参长度,arguments.callee.length是形参长度

在递归中有很好的应该

call、apply与bind在理解的更多相关文章

  1. 对于call,apply,bind 的理解

    JavaScript 中 call().apply().bind() 的用法 之前对与JavaScript中的call,apply,bind这几个方法一直理解的很模糊,今天总结一下. 例1 var n ...

  2. call,apply,bind的理解

    call,apply,bind均是用于改变this指向. 三者相似之处: 1:都是用于改变函数的this指向. 2:第一个参数都是this要指向的对象. 3:都可以通过后面的参数进行对方法的传参. l ...

  3. JS中的apply,call,bind深入理解

    在Javascript中,Function是一种对象.Function对象中的this指向决定于函数被调用的方式.使用apply,call 与 bind 均可以改变函数对象中this的指向,在说区别之 ...

  4. javascript中call()、apply()、bind()的用法终于理解

    其实是一个很简单的东西,认真看十分钟就从一脸懵B 到完全 理解! 先看明白下面: 例1 obj.objAge;  //17 obj.myFun()  //小张年龄undefined 例2 shows( ...

  5. 理解JS中的call、apply、bind方法(*****************************************************************)

    在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:app ...

  6. 深入理解 call,apply 和 bind

    在JavaScript 中,call.apply 和 bind 是 Function 对象自带的三个方法,这三个方法的主要作用是改变函数中的 this 指向,从而可以达到`接花移木`的效果.本文将对这 ...

  7. 【JS】306- 深入理解 call,apply 和 bind

    作者:一像素 链接:https://www.cnblogs.com/onepixel/p/6034307.html 在JavaScript 中,call.apply 和 bind 是 Function ...

  8. call,apply,bind的理解

    2020-03-19 call,apply,bind的理解 先说区别call, apply基本上没什么不一样,唯一不一样的地方是传参方式不同 但是bind和call,apply有区别.bind是重新绑 ...

  9. bind、apply、call的理解

    一直感觉代码中有call和apply就很高大上(看不懂),但是都草草略过,今天非要弄明白!以前总是死记硬背:call.apply.bind 都是用来修改函数中的this,传参时,call是一个个传参, ...

随机推荐

  1. log4j 配置文件 (XML/.properties)

    xml: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configurat ...

  2. IOS 模仿有storyboard的项目控制器的创建

    ● 先加载storyboard文件(Test是storyboard的文件名) UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@ ...

  3. 漫谈 Clustering (5): Hierarchical Clustering

    系列不小心又拖了好久,其实正儿八经的 blog 也好久没有写了,因为比较忙嘛,不过觉得 Hierarchical Clustering 这个话题我能说的东西应该不多,所以还是先写了吧(我准备这次一个公 ...

  4. cuda api查询问题

    在查询CUDA运行时API的时候,我用360极速浏览器的时候搜索结果一直不出来,但是用火狐的话就很流畅,所以建议大家在开发时还是用火狐浏览器.

  5. SpringBoot学习3:springboot整合filter

    整合方式一:通过注解扫描完成 Filter 组件的注册 1.编写filter package com.bjsxt.filter; import javax.servlet.*; import java ...

  6. C/C++程序基础 (八)数据结构

    非递归先序遍历 // 输出, 遍历左子树,遍历右子树 void firstOrder(Node* root) { stack<Node*> leftNodes; Node* curr = ...

  7. js数组中去重对象

    var allCourses = new Array();var coursesId = new Array();function findCourses() { Courses.data().eac ...

  8. HashMap 排序

    本文章,摘抄自:2018黑马程序最新面试题汇总 已知一个 HashMap<Integer,User>集合, User 有 name(String)和 age(int)属性.请写一个方法实现 ...

  9. 【PHP】判断变量是否为控

    1. isset功能:判断变量是否被初始化 说明:它并不会判断变量是否为空,并且可以用来判断数组中元素是否被定义过注意:当使用isset来判断数组元素是否被初始化过时,它的效率比array_key_e ...

  10. java中的继承 (2013-10-11-163 写的日志迁移

    继承:为了解决代码重用 定义: 子类通过继承父类,可以调用父类中非私有的属性和方法,达到重用的目的,通过关键字extends实现:   ################以下为代码演示: class A ...