这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
手写call |apply | bind
01. call
-
call(thisArg, arg1, arg2)- 第一个参数为需要改变的
this指向 - 第二个参数接收一个参数列表
let myCall = function(context){ // 1. 获取需要改变的 this 指向 // 如果有,则为指定的对象,如果没有,则为window // (当然,这里并不严谨) context = context || window // 2. 获取目标函数 // 这里的目标函数就是this // 因为 myCall 是作为一个方法被调用的,例如 [].slice.myCall() // 因此,在函数内部的 this 当然指向调用对象,而这个对象就是目标函数 f slice() // 所以我们 让目标函数成为 context 对象的一个成员方法 // (这里也不严谨) context.fn = this // 3. 获取参数 let args = [...arguments].slice(1) // 4. 让改变 this指向的对象 即context 调用这个函数 let result = context.fn(...args) // 5. 删除这个函数 delete context.fn // 6. 返回结果 return result }对于
context = context || window-
值为
null和undefined的 this 值会自动指向全局对象(浏览器中为window) -
值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
-
即,
Object(context)
if (context === null || context === undefined) { context = window } else { context = Object(context) }
对于
context.fn = thisfn可能跟上下文对象的原属性冲突- 可以使用
Symbol来避免这个冲突
const fn = Symbol('myCall') context[fn] = this; - 第一个参数为需要改变的
02. apply
-
apply(thisArg, [arg1,arg2])-
第一个参数为需要改变的
this指向 -
第二个参数接收一个参数数组(
apply接收一个数组Array,两者同为A开头)实现方式同
call,只是参数形式不一样
let myApply = function(context){ let context = context || window context.fn = this let result // 判断是否第二个参数——第二个参数是数组 if(arguments[1]){ result = context.fn(...arguments[1]) }else { result = context.fn() } delete context.fn return result }注:有许多细节并没有去关注,这里只给出了一个简版的
apply实现,解决方式同上 -
03. bind
-
bind(thisArg)- 第一个参数为需要改变的
this - 该方法会直接返回一个函数
- 第一个参数为需要改变的
-
简单版的
Function.prototype.myBind = function (context) { // 先判断 this 是不是函数 if (typeof this != 'function') { throw new TypeError('Error') } // 把 this 存起来 let that = this // 把参数存起来 let args = Array.prototype.slice.call([...arguments]) // 定义一个函数,后面需要返回的 let returnFunction = function () { // 返回的这个函数里面是改变 this 之后的函数 // 上面的 context 就是改变了 this 之后的调用函数 // 还要把参数全部传入 return that.apply(context, [...args, ...arguments]) } // 返回这个函数 return returnFunction } -
具体一点
bind函数返回的函数,还可以是一个构造函数所以这里要做一个判断,因为构造函数的
this不被任何方式修改,它始终指向其创建的实例对象Function.prototype.myBind = function (context) { // 先判断 this 是不是函数 if (typeof this != 'function') { throw new TypeError('Error') } // 把 this 存起来 let that = this // 把参数存起来 let args = Array.prototype.slice.call([...arguments]) // 定义一个函数,后面需要返回的 let returnFunction = function () { // 当作为构造函数时,this 指向实例,即不需要改变 this 指向 // 当作为普通函数时,this 指向 context // 注:这里的 this 是调用它的this,不是外层myBind的 this if(this instanceof returnFunction){ // 这里判断 this 是不是 returnFunction 的实例 return that.apply(this, [...args, ...arguments]) }else { return that.apply(context, [...args, ...arguments]) } } // 然后修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值 returnFunction.prototype = this.prototype // 返回这个函数 return returnFunction } -
优化后:
Function.prototype.myBind = function (context) { // 先判断 this 是不是函数 if (typeof this != 'function') { throw new Error("被绑定的不是函数") } // 把 this 存起来 let that = this // 把参数存起来 let args = Array.prototype.slice.call([...arguments]) // 定义一个函数,后面需要返回的 let returnFunction = function () { // 当作为构造函数时,this 指向实例,即不需要改变 this 指向 // 当作为普通函数时,this 指向 context // 注:这里的 this 是调用它的this,不是外层myBind的 this return that.apply( this instanceof returnFunction ? this : context, [...args, ...arguments] ) } // 然后修改返回函数的 prototype 为绑定函数的实例的 prototype returnFunction.prototype = Object.create(this.prototype) // 返回这个函数 return returnFunction }当然,这里可能还是有些细节没有考虑到...,望谅解
本人前端小菜鸡,如有不对请谅解+