js中琢磨不透的this指向

函数调用栈:

function baz(){     //当前调用栈:baz,当前调用位置在全局作用域
    console.log('baz');
    bar();         //bar的调用位置
}
function bar(){    //当前调用栈:baz->bar,当前调用位置在baz中
    console.log('bar');
    foo();         //foo的调用位置
}
function foo(){   //当前调用栈:baz->bar->foo,当前调用位置在bar中
    console.log('foo');
}
baz();            //baz的调用位置

断点调试过程中可以看到调用栈的具体信息,在call StacK中可以看到

this绑定规则:


1.默认绑定
独立函数调用,函数中的this默认绑定到全局作用域,

function foo(){
    let a=2;
    console.log(this.a)
}

2.隐式绑定
调用位置是否有上下文,是否被某个对象拥有或者包含,

function foo(){
    console.log(this.a)
}
var obj={
    a: 2,
    foo:foo
}
obj.foo()//2

foo的声明方式是独立函数,但是foo无论是在obj中定义还是添加为引用属性,这个函数严格意义讲都不属于obj对象,然而调用位置会使用obj的上下文来引用函数,因此你可以说函数调用时”拥有“或“包含”它
对象属性引用链中只有上一层或者最后一层在调用位置中起作用

引用丢失:

function foo(){
    console.log(this.a)
}
var obj={
    a: 2,
    foo: foo
}
var bar=obj.foo
var a="global"
bar()//'global'

bar是obj.foo的一个引用,但实际上它引用的是foo函数本身,因此bar()其实是一个不带任何修饰的函数调用,因此应用默认绑定
参数传递是一种隐式赋值,因此我们传入函数是也会被隐式赋值,因此回调函数中也是默认绑定

3.显式绑定

js提供的大多数函数以及我们自定义的函数都可以用call()和apply()方法,
第一个参数是对象,是给this准备的,接着在调用函数时将其绑定到this,因此可以直接指定this的绑定对象
 

function foo(){
    console.log(this.a)
}
var obj = {
    a: 2
}
foo.call(obj)//2

如果你传入了一个原始值来当做this的绑定对象,这个原始值会被转化为他的对象形式(new String(),new Boolean(),new Number()),这通常称为“装箱”
显式绑定仍然无法解决绑定丢失的问题。
但是显式绑定的一个变种可以解决这个问题。
1.硬绑定
 

function foo(){
    console.log(this.a)
}
var obj = {
    a: 2,
}
var bar = function(){
    foo.call(obj)
}
bar()//2
setTimeout(bar, 1000)//2
bar.call(window)//2

创建函数bar(),内部手动调用foo.call(obj),因此强制把foo的this绑定到了obj,无论之后如何调用bar(),他总会手动在obj上调用foo,这种绑定是一种显式的强制绑定,称之为“硬绑定”
由于硬绑定非常常见,因此ES5提供了内置的方法Function.prototype.bind,用法如下:
 

function foo(something){
    console.log(this.a, something)
    return this.a+something
}
var obj = { a: 2 }
var bar = foo.bind(obj)
var b = bar(3) //5

2.API调用的上下文
第三方库的许多函数,以及js语言和宿主环境中许多的内置函数,都提供了一个可选的参数,被称为“上下文”(context),其作用和bind()一样,确保你的回调函数使用指定的this
 

function foo(){
    console.log(el, this.id)
}
var obj = { id: 'awesome'}
//调用foo时把this绑定到obj
[1, 2, 3].forEach(foo, obj)//1 awesome 2 awesome 3 awesome

4.new绑定

所有函数都可以使用new来调用,这种函数调用被称为构造函数调用,实际上并不存在所谓的“构造函数”,只是对于函数的“构造调用”
使用new来调用函数,会执行以下操作:
1.创建(构造)一个全新的对象
2.这个新对象会被执行[[prototype]]连接
3.这个新对象会被绑定到函数调用的this
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

function foo(a){
    this.a = a
}
var bar = new foo(2)
console.log(bar.a)//2

判断this
1.函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。var bar = new foo()
2.函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是,this绑定的是指定的对象。var bar = foo.call(obj)
3.函数是否在某个上下文对象中调用(隐式绑定)?如果是,this绑定的是那个上下文对象
4.如果都不是的话,使用默认绑定,如果在严格模式绑定到undefined,否则绑定到全局对象var bar = foo()

绑定this例外:
1.被忽略的this,如果把null,undefined作为this的绑定对象传入call,apply,bind,这些值会被忽略,使用默认绑定规则

更安全的this
一种安全的做法是传入一个特殊的对象,把this绑定到这个对象不会对你的程序产生任何副作用,他是一个空的非委托的对象。
js中创建空对象的方法:Object.create(null),它和{}很像,但是并不会创建prototype这个委托,所有他比{}更空,可以使用ø(Alt+o或者option+o打印这个符号)

function foo(){
    console.log("a: "+a+",b: "+b)
}
var ø = Object.create(null)
foo.apply(ø, [2, 3])
var bar = foo.bind(ø, 2)
bar(3)//a:2,b:3

2.间接引用

function foo(){
    console.log(this.a)
}
var a = 2
var o = { a: 3, foo: foo }
var p = { a: 4 }
o.foo()//3
(p.foo = o.foo)(); //2

赋值表达式p.foo=o.foo的返回值时目标函数的引用,因此调用位置是foo()而不是p.foo()或o.foo(),所以会应用默认绑定
3.软绑定
硬绑定把this强制绑定到指定的对象(除了使用new时),防止函数调用应用默认绑定的规则,但是硬绑定降低了函数的灵活性,使用硬绑定之后无法使用隐式绑定或显式绑定来修改this
入股可以给默认绑定指定一个全局对象或者undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定和显式绑定修改this的能力。
可以通过一种被称为软绑定的方法实现我们的效果:

if(!Function.prototype.softBind){
    Function.prototype.softBind = function(obj){
        var fn = this;
        var curried = [].slice.call(arguments,1);
        var bound = function(){
            return fn.apply(
                (!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments)
            );
        };
        bound.prototype = Object.create(fn.prototype);
        return bound;
    }
}

softBind会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或undefined,那就把指定到默认对象obj绑定到this,否则不会修改this。
 

function foo(){
    console.log("name: "+this.name)
}
var obj1 = { name: obj1 },
    obj2 = { name: obj2 },
    obj3 = { name: obj3 };
var fooOBJ = foo.softBind(obj1);
fooOBJ();//name:obj1
obj2.foo = foo.softBind(obj)
obj2.foo(); //name:obj2
fooOBJ.call(obj3);//name:obj3
setTimeout(obj2.foo, 100)//name:obj1

可以看出软绑定的foo(),可以手动修改this绑定到obj2或obj3上,但是如果应用默认绑定,这回将this绑定到obj1上。

箭头函数:
箭头函数不使用this的四种标准,而是更加外层(函数或全局)作用域来决定this,箭头函数的绑定无法修改

function foo(){
    return (a)=>{
        console.log(this.a)//this继承自foo
    }
}
var obj1 = { a: 1 }
var obj2 = { a: 2 }
var bar=foo.call(obj1)
bar.call(obj2)//1


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值