谈谈JavaScript中的call、apply和bind ゞ 浴缸里的玫瑰 2022-10-01 09:56 110阅读 0赞 在`JavaScript`中,如果想要改变当前函数调用的上下文对象的时候,我们都会联想到`call、apply和bind`。比如下面? var name = 'window name'; var obj = { name: 'call_me_R' }; function sayName(){ console.log(this.name); } sayName(); // window name sayName.call(obj); // call_me_R 复制代码 那么,`call, apply和bind`有什么区别呢? ### call,apply和bind的区别 ### 在说区别之前,先简单的说下三者的共同之处吧: 1. 都是用来改变函数的this对象的指向 2. 第一个参数都是this要指向的对象 3. 都可以利用后续参数进行传参 下面说下区别: #### 参数的传递 #### 参考下[MDN web docs -- Function][]: `call`方法传参是传一个或者是多个参数,第一个参数是指定的对象,如开篇的`obj`。 func.call(thisArg, arg1, arg2, ...) 复制代码 `apply`方法传参是传一个或两个参数,第一个参数是指定的对象,第二个参数是一个数组或者类数组对象。 func.apply(thisArg, [argsArray]) 复制代码 `bind`方法传参是传一个或者多个参数,跟`call`方法传递参数一样。 func.bind(this.thisArg, arg1, arg2, ...) 复制代码 简言之,`call`和`bind`传参一样;`apply`如果要传第二个参数的话,应该传递一个类数组。 #### 调用后是否立执行 #### **call和apply**在函数调用它们之后,会立即执行这个函数;而函数调用**bind**之后,会返回调用函数的引用,如果要执行的话,需要执行返回的函数引用。 变动下开篇的`demo`代码,会比较容易理解: var name = 'window name'; var obj = { name: 'call_me_R' }; function sayName(){ console.log(this.name); } sayName(); // window name sayName.call(obj); // call_me_R sayName.apply(obj); // call_me_R console.log('---divided line---'); var _sayName = sayName.bind(obj); _sayName(); // call_me_R 复制代码 在笔者看来,`call, apply 和 bind`的区分点主要是上面的这两点,欢迎有想法的读者进行补充~? ### 手写call, apply, bind方法 ### 这里是简单的实现下相关方法的封装,为了简洁,我这里尽量使用了ES6的语法进行编写,详细的参考代码可以直接戳**airuikun**大牛的[airuikun/Weekly-FE-Interview issues][airuikun_Weekly-FE-Interview issues]。 #### call方法实现 #### 在上面的了解中,我们很清楚了`call`的传参格式和调用执行方式,那么就有了下面的实现方法: Function.prototype.call2 = function(context, ...args){ context = context || window; // 因为传递过来的context有可能是null context.fn = this; // 让fn的上下文为context const result = context.fn(...args); delete context.fn; return result; // 因为有可能this函数会有返回值return } 复制代码 我们来测试下: var name = 'window name'; var obj = { name: 'call_me_R' }; // Function.prototype.call2 is here ... function sayName(a){ console.log(a + this.name); return this; } sayName(''); // window name var _this = sayName.call2(obj, 'hello '); // hello call_me_R console.log(_this); // {name: "call_me_R"} 复制代码 #### apply方法实现 #### `apply`方法和`call`方法差不多,区分点是`apply`第二个参数是传递数组: Function.prototype.apply2 = function(context, arr){ context = context || window; // 因为传递过来的context有可能是null context.fn = this; // 让fn的上下文为context arr = arr || []; // 对传进来的数组参数进行处理 const result = context.fn(...arr); // 相当于context.fn(arguments[1], arguments[2], ...) delete context.fn; return result; // 因为有可能this函数会有返回值return } 复制代码 同样的,我们来测试下: var name = 'window name'; var obj = { name: 'call_me_R' }; // Function.prototype.apply2 is here ... function sayName(){ console.log((arguments[0] || '') + this.name); return this; } sayName(); // window name var _this = sayName.apply2(obj, ['hello ']); // hello call_me_R console.log(_this); // {name: "call_me_R"} 复制代码 #### bind方法实现 #### `bind`的实现和上面的两种就有些差别,虽然和`call`传参相同,但是`bind`被调用后返回的是调用函数的指针。那么,这就说明`bind`内部是返回一个函数,思路打开了: Function.prototype.bind2 = function(context, ...args){ var fn = this; return function () { // 这里不能使用箭头函数,不然参数arguments的指向就很尴尬了,指向父函数的参数 fn.call(context, ...args, ...arguments); } } 复制代码 我们还是来测试一下: var name = 'window name'; var obj = { name: 'call_me_R' }; // Function.prototype.bind2 is here ... function sayName(){ console.log((arguments[0] || '') + this.name + (arguments[1] || '')); } sayName(); // window name sayName.bind2(obj, 'hello ')(); // hello call_me_R sayName.bind2(obj, 'hello ')('!'); // hello call_me_R! 复制代码 美滋滋?,成功地简单实现了`call、apply和bind`的方法,那么你可能会对上面的某些代码有疑问❓ #### 疑惑点 #### **1. 问:call中为什么说 context.fn = this; // 让fn的上下文为context 呢?** 答: 我们先来看看下面这段代码-- var name = 'window name'; var obj = { name: 'call_me_R', sayHi: function() { console.log('Hello ' + this.name); } }; obj.sayHi(); // Hello call_me_R window.fn = obj.sayHi; window.fn(); // Hello window name 复制代码 嗯,神奇了一丢丢,操作`window.fn = obj.sayHi;`改变了`this`的指向,也就是`this`由指向`obj`改为指向`window`了。 简单来说:**this**的值并不是由函数定义放在哪个对象里面决定的,而是函数执行时由谁来唤起来决定的。 **2. 问:bind中返回的参数为什么是传递(context, ...args, ...arguments), 而不是(context, ...args)呢?** 答: 这是为了包含返回函数也能传参的情况,也就是`bind()()`中的第二个括号可以传递参数。 ### call和apply哪个好? ### 据调查--[call和apply的性能对比][call_apply],在分不同传参的情况下,**call的性能是优于apply的**。不过在现代的高版本浏览器上面,两者的差异并不大。 而在兼容性方面,两者都好啦,别说IE了哈。 在使用的方面还是得按照需求来使用`call和apply`,毕竟技术都在更新。适合业务的就是最好的~囧 ### 后话 ### 文章首发:[github.com/reng99/blog…][github.com_reng99_blog] 更多内容:[github.com/reng99/blog…][github.com_reng99_blog 1] **客官可以star下[github的博文仓库][github.com_reng99_blog 1]否,欢迎提意见共同成长啊~**? ### 参考 ### [MDN web docs -- Function][] [airuikun/Weekly-FE-Interview issues][airuikun_Weekly-FE-Interview issues] 《JavaScript高级程序设计》 转载于:https://juejin.im/post/5cf648c45188253a2b01ccb1 [MDN web docs -- Function]: https://link.juejin.im?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FFunction [airuikun_Weekly-FE-Interview issues]: https://link.juejin.im?target=https%3A%2F%2Fgithub.com%2Fairuikun%2FWeekly-FE-Interview%2Fissues [call_apply]: https://link.juejin.im?target=https%3A%2F%2Fgithub.com%2Fnoneven%2F__%2Fissues%2F6 [github.com_reng99_blog]: https://link.juejin.im?target=https%3A%2F%2Fgithub.com%2Freng99%2Fblogs%2Fissues%2F29 [github.com_reng99_blog 1]: https://link.juejin.im?target=https%3A%2F%2Fgithub.com%2Freng99%2Fblogs
还没有评论,来说两句吧...