就是通过 call,apply,bind 的方式,显式的指定 this 所指向的对象。箭头函数无法通过call、apply,bind改变this指向
new 绑定
顾名思义
使用 new 来调用函数,会自动执行下面的操作:
创建一个新对象
将构造函数的作用域赋值给新对象,即 this 指向这个新对象
执行构造函数中的代码
返回新对象
因此,我们使用 new 来调用函数的时候,就会新对象绑定到这个函数的 this 上。
绑定优先级
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
call/apply/bind 的实现
三者都可以用于改变 this 的指向,区别在于
call 接受目标函数和需要枚举参数,并立刻执行
apply 接受目标函数和参数数组,并立刻执行
bind 接受目标函数和需要枚举参数,返回新函数,不会立刻执行
call 和 apply 如何实现呢?利用隐式绑定的特性,即 obj.xxx(),分三步走:
将函数设为对象的属性
执行该函数
删除该函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Function.prototype.call = function (context) { // 没有传时,指向 window var context = context || window // 例如 func.call(obj), 那么 this 就是函数自身 context.fn = this // 取得参数 var args = [] for (var i = 1; i < arguments.length; i++) { args.push('arguments[' + i + ']') } // 执行 var result = eval('context.fn(' + args + ')') // 从对象上删除 delete context.fn // 返回 result return result }
apply 的实现是类似的,只是参数变成了一个显示传入的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Function.prototype.apply = function (context, arr) { var context = Object(context) || window context.fn = this
var result if (!arr) { result = context.fn() } else { var args = [] for (var i = 0, len = arr.length; i < len; i++) { args.push('arr[' + i + ']') } result = eval('context.fn(' + args + ')') }
delete context.fn return result }
bind 的实现,可以借助柯里化的思路,将传入的 obj 和参数保存在内存中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Function.prototype.bind = function () { var self = this// 保存原函数 // 保存需要绑定的this上下文(第一个参数,就是传进来的对象) var context = [].shift.call(arguments) // 剩余的参数转为数组 var args = [].slice.call(arguments)
returnfunction () { // 返回一个新函数 // 合并参数 var pars = [].concat.call(args, [].slice.call(arguments)) self.apply(context, pars) } }
注意,bind 方法可以接受并保存多余的参数,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let obj = { a: 3, func(a, b) { console.log(this.a + '' + a + b) }, } // 执行 obj.func(1, 2) // => 312
// 此时 在 window 上绑定一个 a window.a = 1 const f = obj.func.bind(window, 1) // 指定第一个参数