💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## apply和call讲解 为什么这两个放在一起,使用过这两个方法的都知道,他们在使用上,传参方式不一样,其他都一样的;但是bind就要复杂些了。 ### 通俗话**原理** * 就是把A对象作用域(this)传递到B(函数)对象作用域中执行,B函数作用域中执行取决于在哪个执行环境(this)中。 * 埋个伏笔,先来看一下下面继承,来对应一下上面这个描述,FB中this即是objA(注意:当FB原来this即是window对象),说明FB在经过objA.FB = FB时,FB环境已经改变。这个等阅读完回过头看,就会发现和call实现原理好像挺相似的。 ``` js function FB(age){ console.log('FB:this=',this) // {name: 'vvmily', FB: ƒ} this.age = age console.log(this.name,this.age) // vvmily 18 } var objA = { name: 'vvmily' } objA.FB = FB // objA.prototype.FB = FB objA.FB(18) ``` * 下面通过call方式,在表面可以看出objA.FB = FB与FB.call(this)作用一样,把FB的执行作用域环境改变为FA的作用域(this)环境。 ``` js function FB(age){ this.age = age console.log(this.name, this.age) // vvmily 19 } function FA(){ this.name = 'vvmily' FB.call(this,19) } const a = new FA() ``` ### 总结 有了总结,知道call做了什么事情,才更好的实现,不是吗。 1. 将当前执行作用域赋予另一个函数作用域 FB(this) => FA(this) 2. 另一个函数作用域执行结果返回(挂在)当前作用域 ### 手写实现 ``` js function MyCall(context, ...args){ context = context || window // MyCall所在执行作用域的this,并非MyCall自己的this args = args || [] context.B_Fn = this // 保存当前MyCall执行作用域 const result = context.B_Fn(...args) // 执行当前作用域,并拿到结果,并且结果直接挂在执行的作用域中,而非MyCall作用域哦 delete context.B_Fn // 防止污染 return result } ``` 检验一下是否正确,当然是成功了 ``` js Function.prototype.MyCall = MyCall function FB(age){ this.age = age console.log(this.name, this.age) // vvmily 19 } function FA(){ this.name = 'vvmily' FB.MyCall(this,19) } const a = new FA() ``` ### apply手动实现 请仔细对比,基本args的区别,就是参数,这里FB.MyCall(this,[19])第二个参数为数组即可 ``` js function MyApply(context, args){ context = context || window // MyCall所在执行作用域的this,并非MyCall自己的this args = args || [] context.B_Fn = this // 保存当前MyCall执行作用域 const result = context.B_Fn(...args) // 执行当前作用域,并拿到结果,并且结果直接挂在执行的作用域中,而非MyCall作用域哦 delete context.B_Fn // 防止污染 return result } ``` ## bind讲解 通俗讲:将某个(对象,如objB)作用域作为(将要返回的)新函数体作为执行环境。 先来看看下面的代码,感受一下上面这句话。 ``` js const objB = { name: 'vvmily', getName: function(){ // console.log("getName is name:",this.name) return this.name } } const FA = objB.getName console.log(FA()) // undefined,为什么呢,FA执行作用域是window,即getName函数作用域this===window,而在window.name可是没有值的。 console.log(FA.bind(objB)()) // vvmily ,将objB作用域作为返回新函数体的执行环境 console.log(objB.getName()) // vvmily ,此时执行作用域是objB对象内,getName执行环境是objB作用域内,即this===objB。 FA.bind(obj) ``` ## 手动实现 目的:最终肯定返回一个函数体(如:myBind) ### 用FA.bind(objB)()分析: 1. 当FA.bind(objB)时,需要干点什么呢?肯定要保存objB作用域的数据 2. 该函数体需要包含哪些数据呢?包含FA.bind(objB)执行时的**执行作用域**和**入参**; 3. 而最终返回的函数体,执行时,还能访问到上次FA.bind(objB)函数中的变量,则需要用到**闭包**了; 4. 接下来通过函数体如何访问objB信息?这里需要借助一个中间构造函数Fn了,通过Fn原型继承FA.bind(objB)的原型,即Fn.prototype = self.prototype; 5. 返回函数体原型继承Fn实例,即myBind.prototype = new Fn()。 注意:FA.bind(objB)就是objB的作用域,访问的就是objB数据。 ``` js function MyBind(context,...args){ let self = this; // 2 args = args || []; // 2 let Fn = function(){}; // 4 let myBind = function(){ // ... } Fn.prototype = self.prototype // 4 myBind.prototype = new Fn() // 5 return myBind } ``` ### 实现myBind函数体 * 这里需要借助前面实现的call或者apply方法 ``` js // ... let myBind = function(...params){ const allArgs = [...args, ...params] // 参数合并 return self.call(context, ...allArgs) } // ... ``` * 完整代码 ``` js function MyBind(context,...args){ let self = this; args = args || []; let Fn = function(){}; let myBind = function(...params){ const allArgs = [...args, ...params] // 参数合并 return self.call((this instanceof Fn ? this : context), ...allArgs) } Fn.prototype = self.prototype myBind.prototype = new Fn() return myBind } ``` ## 最终总结 call、apply 和 bind 的功能相似,什么时候使用 bind 呢?这个确实也没有明确的规定,只要知道其原理,怎么选择都不重要,主要的区别无非就是 call、apply 绑定后是立即执行,而 bind 绑定后是返回函数体,需要调用即可。