都是改变this的方式。
- 显示绑定。apply,call,this
- 隐式绑定 Obj.func
- new 操作符
- 默认绑定 严格模式undefined 非严格window
bind的实现依靠apply/call, 而apply/call的实现,依靠隐式绑定,谁调用了,this指向谁。
bind
bind函数特征:
- 可以指定this
- 可以传入参数
- 返回新函数照样可以接受参数,使用上跟原函数一致 (科里化)
- 绑定函数也能使用 new 操作符创建对象:这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的其他参数仍然被提供给模拟函数 ```javascript // 不能用箭头函数,否则指向了外层对象。使用普通函数,谁调用函数内的this就指向谁 Function.prototype.myBind = function () { if (typeof this !== ‘function’) { throw new Error(‘Function.prototype.myBind - what is trying to be bound is not callable’) } const self = this // 保存原始函数 const args = Array.prototype.slice.call(arguments) // 类数组转数组 const newThis = args.shift() // 取出参数第一个,即新的this指向 const fNOP = function(){} // 创建一个新的空函数 // 这里和上面使用了两种方式来接受参数,函数内参数argumets和拓展符…args,都可以取得参数,只是拓展符是es6才引入,兼容性差,尽量都使用arguments获取 function fBind (…params) { // bind返回的函数,继续接受它自己的参数 // 被new调用了,则this是构造函数的实例,则使用new所生成的this self.apply(this instanceof fBind ? this : newThis, args.concat(params)) } // 使用中间一层过渡而不是直接fBind.prototype = self.prototype, 是为在原始函数修改prototype的时候不影响已经被myBind过的函数 fNOP.prototype = self.prototype // 把原来函数的原型绑定到空函数的原型上 // fNop所构造的实例作为新函数的原型 fBind.prototype = new fNOP() //(这其实就多了一层吧,new fNop()是个空对象,原来在函数的prototype上的方法属性,现在是在新函数的prototype(空对象)的proto上) // 上述的两行,也可以使用ES5语法,fBind.prototype = Object.create(self.prototype) return fBind }
// tip: 不要使用name来测试,因为this.name会默认显示当前函数名 // test-1 var username = ‘li’ const test1 = function (sex) { console.log(‘this.username =’ + this.username, sex) } const test2 = test1.myBind({username: ‘qm’}, ‘male’) const test3 = (age) => console.log(this.username + “=====”, age) // 箭头函数无法被bind,因为箭头函数的this在书写的时候,已经被词法作用域确定了,无法改变 const test4 = test3.myBind({username: ‘tingting’}) test1(‘female’) // li famale test2() //qm male test3(20) // li ====== 20 test4(18, 20) // li ===== 18
// test-2 var username = ‘li’ const aa = function () { console.log(this.username)} const bb = aa.myBind({username: ‘qm’}) aa() // li bb() // qm const cc = new bb() // undefined 即不是全局的username,也不是myBind传入的username,说明当调用bb的时候,而是使用了新的this cc // {proto: aa}
<a name="Gxdsp"></a>
# apply
```javascript
func.apply(context, [])
apply改变this的关键在于用把原函数设置为context的一个属性,然后调用原函数,这样根据隐式绑定的原则,context调用了函数,那么this就指向了context。
// 这种写法,参数由arguments来获得的,那么call和apply都是一模一样的实现,只是arguments这种获取参数的方式,不利于判断传入的参数到底是数组,还是一个个的。
// 而参数的区别也是call和apply的主要区别
// Function.prototype.myApply = function (context, arg) { 用形参的方式,判断arg是不是Array就行了
Function.prototype.myApply = function () {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
const args = Array.prototype.slice.call(arguments)
const newThis = args.shift()
const fnSymbol = Symbol() // 保证key的唯一性,不会影响到可能的已有属性
newThis[fnSymbol] = this
const result = newThis[fnSymbol](...args)
delete newThis[fnSymbol]
return result
}
// test
var username = 'li'
const aa = function (test) { console.log(this.username, test) }
aa() // li undefined
const bb = aa.myApply({username: 'qm'}, [1,2,3,4]) // qm [1,2,3,4]
call
跟apply一样,只是传入的参数是一个个的, 实现如上