this指向解析
this绑定
函数内部this的指向,取决于调用的位置,调用位置就是函数在代码中被调用的位置(而不是声明的位置),它在当前正在执行的函数的前一个调用栈帧中,也就是处于栈顶的下一个栈元素。
默认绑定
函数调用时,默认指向全局对象,window或者global,如果是严格模式则指向undefined,严格模式只影响当前作用域this的绑定
隐式绑定
当函数引用有上下文对象时,this会被绑定到.前面的对象。
隐式绑定的函数如果传给其它变量或是作为其他函数的形参传入,会丢失绑定,这叫隐式丢失。this会被绑定到全局对象或是undefined。
显式绑定
使用call和apply可以在函数调用时,将对象绑定到this上。
function foo(greeting){
return this.name + greeting;
}
const obj = {
name: '石晓波'
}
//实现bind,可以显式地绑定函数的this到指定的对象上
function bind(fn, obj){
return function(){
return fn.apply(obj, arguments);
}
}
const greet = bind(foo, obj);
console.log(greet('hello'))
new 绑定
构造函数使用new操作符时会自动执行以下操作:
- 创建一个新对象。
- 该对象会被执行
[[Prototype]],也就是原型指向构造函数的原型。 - 该对象会被绑定到当前构造函数运行时的this。
- 如果构造函数返回的是对象,则返回该对象,否则返回新创建的对象。
function create(){
const obj = Object.create(null);
const Con = [].shift.call(arguments);
obj.__proto__ = Con.plrototype;
const ret = Con.apply(obj, arguments);
return ret instanceof Object ? ret : obj;
}
优先级
new => 显式绑定 => 隐式绑定 => 默认绑定
绑定例外
被忽略的this
把nulll或undefined作为绑定对象传入call、apply或bind,这些值就会被忽略,实际应用的是默认规则。
传入null的场景有以下两个:
- 将传入的数组参数展开,并当做参数传给函数
- 对参数进行柯里化(预先设置一些参数)
function foo(a,b){
}
//把数组展开成一个个参数
foo.apply(null, [1,2])
//使用bind进行柯里化
const bar = foo.bind(null, 1);
bar(2)//1,2
使用null来忽略指定this的绑定对象会有一些副作用,如果内部使用了this,this会指向全局对象,严格模式下指向undefined。
最好的方法是指定this的绑定对象为一个plain object,即纯的对象
function foo(a,b){
console.log(a,b)
}
const obj = Object.create(null);
foo.apply(obj, [1,2])_
间接引用
间接引用下,函数作用的是默认绑定规则,如将函数作为参数传递或者传递给其他变量或属性,在传递过程中丢失了绑定对象。
软绑定
使用硬绑定后可以将this绑定到某个对象,防止其应用默认绑定,但是会降低函数的灵活性,无法隐式或者显式地更改this的绑定对象。
使用软绑定可以实现:
- 函数软绑定后,如果是默认绑定规则,this将指定软绑定传入的对象。
- 如果是隐式板顶,则this指向
.前面的对象。 - 如果是显式绑定,则this指向指定的对象。
代码实现:
//软绑定
if(!Function.prototype.softBind){
Function.prototype.softBind = function(obj){
let fn = this;
const curried = [].slice.call(arguments, 1);
const bound = function(){
//如果this为undefined或指向window,使用软绑定的对象,否则使用其他绑定规则的绑定对象
const _this =!this || this === global ? obj : this;
//返回的函数的原型和调用函数的原型一致
bound.prototype = fn.prototype;
fn.apply(_this, cyrried.concat(curried, arguments))
}
reutrn bound;
}
}
const obj = {name: 'obj'}
const obj2 = {name: 'obj2'}
const obj3 = {name: 'obj3'}
function foo(){
console.log('name:' + this.name);
}
const fooBind = foo.softBind(obj)
//默认绑定规则
fooBind();// name: obj
//隐式绑定规则
obj2.foo = foo.softBind(obj);
obj2.foo()//name:obj2
//显示绑定规则
fooBind.call(obj3)//name:obj3
this词法
箭头函数无法应用上述四条规则,而是根据外层作用域决定this。
箭头函数的this指向无法被修改。
使用词法作用域替代this的使用:通过一个变量保存当前的this,然后提供给其他作用域使用
箭头函数this的总结
- 箭头函数不绑定this。
- 箭头函数中this寻值行为和普通变量相同,沿着作用域逐级寻找,因为没有自身的this,所以只能根据作用域链向上层查找。知道招待一个绑定了this的函数作用域。并指向调用该普通函数的对象。
- 箭头函数无法通过call、apply、bind来修改this的指向。
- 改变箭头函数所在的作用域可以修改箭头函数中的this。
//改变封包环境来改变箭头函数中的this
function closure(){
return () => {
console.log(this)
}
}
const obj = {name: '石晓波'}
closure.bind(obj)();