关于this

this 指向(你不知道的javascript版)

一、关于this

为什么我们要用this

         this相比于显示的传递上下文对象,会更简洁并且易于复用

对this的误解

1)this并不指向它本身

function foo(num) {
    console.log("foo:"+num);
    this.count++; //在无意中创建了一个全局变量,值为NaN
}
foo.count = 0;  //这里确实是向函数对象foo中添加了一个新属性count,但是函数中的this.count++中的this并不指向这个count
var i;

for(i =0;i<10;i++) {
   if(i<5){
   foo(i);
   }
}
//foo:6
//foo:7
//foo:8
//foo:9

//foo被调用了多少次?
console.log(foo.count);//0 

因为 执行foo.count =0 时,确实向函数对象foo中添加了个属性count,但是函数内部的代码this.count中的this并不是指向那个函数对象,若要强制使this指向函数本身,可将foo(i) 改为 foo.call(foo,i) 后面会解释具体原理

2)this并不指向函数的作用域

这个说法在某种情况下是正确的,在某种情况下是错误的,this在任何情况下都不指向函数的词法作用域

3)this 到底是什么

当一个函数调用时,会创建一个活动记录(执行上下文)。这个记录包含函数在哪里被调用(调用栈),函数的调用方式,传入的参数等信息。this就是这个记录的一个属性,会在函数执行时用到。

this是在运行时绑定的,并不是在编写时绑定的,它的上下文取决于函数调用时的各种条件,this的绑定和函数声明的位置没有任何的关系,只取决于函数的调用方式。

this实际上就是在函数调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

二 、this的全面解析

调用位置

调用位置: 函数在代码中被调用的位置(并不是声明的位置),需要分析调用栈(就是为了到达当前执行位置所调用的所有函数)

function baz() {
    //当前的调用栈是baz
    //因此当前的调用位置是全局作用域
    conlose.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的调用位置

绑定规则

1.默认绑定
function foo() {
console.log(this.a);
}
var a = 2;
foo();//2

我们可以看到this.a被解析成了全局变量a,在本例中,函数调用应用了this的默认绑定,因此this指向全局变量,通过分析调用位置来看foo()是如何调用的,在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他的规则

在严格模式下,则不能将全局对象用于默认的绑定,因此this会绑定到undefined

且严格模式是指要函数体处于严格模式,而不是调用位置处于严格模式

2.隐式绑定

要考虑调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含

function foo() {
    console.log(this.a):
}

var obj = {
    a : 2,
    foo: foo
};
obj.foo();//2
//调用位置会使用obj上下文来引用函数,因此可以说函数被调用时,obj对象“拥有”或“包含”函数引用

当函数有上下文对象时,隐形绑定规则会把函数调用中的this绑定到这个上下文对象中。因为调用foo()时,this被绑定到obj中,因此this.a和obj.a是一样的

分析隐式绑定的时候,我们必须在一个对象的内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)的绑定到这个对象中

对象属性引用链中只有最顶层或者最后一层在调用位置起作用 如:

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

var obj2 = {
    a:42,
    foo:foo
};
var obj1 = {
    a:2,
    obj2:obj2
};
obj1.obj2.foo();//42     这里指42,是最顶层即最后一层的obj2,起作用

隐式丢失 :一个常见的this 的绑定问题就是 被隐式绑定的函数会丢失绑定对象,也就是说,他会应用默认绑定,从而把this 绑定到全局对象上或者undefined,取决于是否在严格模式 如:

function foo(){
    console.log(this.a);
 }
 var obj = {
     a :2,
     foo:foo
 };
var bar = obj.foo;//函数别名
var a = "global";
bar();//"global"//这里的bar()实际上是一个不带任何修饰的函数,所以应用了默认绑定

虽然bar 是obj 的引用 ,但实际上,它引用的是foo函数本身,因为此时,bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

传入回调函数时,会发生一种更意外的情况 如:

function foo() {
    console.log(this.a);
 }
 function doFoo(fn){
     fn();
 }
 var obj = {
    a :2,
    foo:foo
 };
var a = "global";
doFoo(obj.foo);//"global"   参数传递其实就是一种隐式传递,因此我们传入的参数会被隐式赋值,所以结果和上一个一样

将参数传入语言内置函数和上面自己声明的函数, 显示的结果都一样

3.显式绑定

关于call(),apply(),bind() 请看这篇文章

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

foo.call(obj);//2
//通过foo.call() 我们可以在调用foo时强制把它的this  绑定到obj 上

call() 与 apply(功能一样) 传参不一样

硬绑定:

硬绑定的典型应用场景就是创建一个对象,传入所有的参数并返回接收到的所有值

function foo(something) {
    console.log(this.a,something);//2 3
    return this.a + something
}

var obj = {
    a:2
}

var bar = function() {
    return foo.apply(obj,arguments)
}

var b = bar(3)
console.log(b); //5

由于硬绑定是一种非常有用的模式,所以在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); //2  3
console.log(b);//5

bind(…)会返回一个硬编码的函数,他会把你指定的参数设置为this 的上下文对象并调用原始函数

4.new绑定(手动实现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
//使用 new  来调用foo(..)时,我们会构造一个新对象并把它绑定到
//foo(..)调用中的this上

绑定优先级

new绑定 -》 显示绑定 -》 隐式绑定 -》 默认绑定

判断this

1.函数是否new绑定,若是,this绑定的是新创建的对象
2.函数是否为显示绑定,若是,this绑定指定的对象
3.函数是否在某个上下文对象中调用隐式绑定?若是,则绑定那个上下文对象
4.都不是的话 默认绑定 严格模式 绑定undefined 非严格模式
绑定到全局对象

绑定例外

被忽略的this

当你把null 或者undefine 作为this的绑定对象传入call、apply或者bind,这些值在调用时就会被忽略,实际应用的是默认绑定的规则。

但是如果某个函数确实使用了this,那默认绑定规则会将this绑定到全局对象,这将会导致修改全局对象。

若希望this 指向为空 可以考虑 :

function (a,b) {
   console.log("a:"+a+",b:" +b);
}

//我们的DMZ空对象
var k = Object.create(null);
//把数组展开成参数
foo.apply(k,[2,3]);//a:2,b:3
//使用bind(..)进行柯里化
var bar = foo.bind(k,2);
bar(3); // a:2,b:3

间接引用

有可能无意中创建一个函数的间接引用,这时,调用这个函数会应用默认绑定 如:

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
//此赋值表达式的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo 
//或者o.foo 

软绑定

硬绑定会大大降低函数的灵活性,使用硬绑定后就无法使用隐式绑定或者显示绑定来修改this

如果给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式和显示修改this的能力,这就是软绑定

if(!Function.prototype.softBind){
    Function.prototype.softBind=function(obj){//先通过判断,如果函数的原型上没有softBind()这个方法,则添加它
        var fn=this;
        var args=Array.prototype.slice.call(arguments,1);//获取传入的外部参数,这样做是为了函数柯里化
        var bound=function(){
            return fn.apply( //首先判断调用软绑定之后的函数的调用位置,或者说它的this的指向
            	//!this(this指向undefined)或者this===(window||global)(this指向全局对象),那么就将函数的this绑定到传入softBind中的参数obj上
            	//如果此时this不指向undefind或者全局对象,那么就将this绑定到现在正在指向的函数(即隐式绑定或显式绑定)
                (!this||this===(window||global))?obj:this,//
                args.concat.apply(args,arguments)
            );
        };
        bound.prototype=Object.create(fn.prototype);
        return bound;
    };
}              

软绑定的用法

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" 在这里软绑定生效了,成功修改了this的指向,将this绑定到了obj1上

obj2.foo=foo.softBind(obj1);
obj2.foo();//"name: obj2" 在这里软绑定的this指向成功被隐式绑定修改了,绑定到了obj2上

fooOBJ.call(obj3);//"name: obj3" 在这里软绑定的this指向成功被硬绑定修改了,绑定到了obj3上

setTimeout(obj2.foo,1000);//"name: obj1"
/*回调函数相当于一个隐式的传参,如果没有软绑定的话,这里将会应用默认绑定将this绑定到全局环
境上,但有软绑定,这里this还是指向obj1*/

this词法

箭头函数 不适用前面所说的this的四条标准规则,而是根据外层(函数或者全局)作用域来决定this。具体来说,箭头函数继承外层函数调用的this绑定(无论this绑定到啥)

function foo() {
   //返回一个箭头函数
   return (a) => {
     //this 继承来自foo()
     console.log(this.a);
     };
 }
 var obj1 = {
     a:2
};
var  obj2 = {
    a:3
 };
 var bar = foo.call(obj1);
 bar.call(obj2);//是2 不是3

foo()内部创建的箭头函数会捕获调用时foo()的this,由于foo()的this 绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改(new也不行)

最后我们会得出结论,要判断一个运行函数中的this指向的时候,就需要找到这个函数的直接调用位置,找到之后用以下四个规则来判断this的绑定对象

1、由new调用?绑定到新创建的对象
2、由call或者apply(或者bind)调用?绑定到指定的对象
3、由上下文对象调用?绑定到那个上下文对象
4、默认:在严格模式下绑定到undefined,否则绑定到全局对象

箭头函数的this(箭头函数并不会使用四条标准的绑定规则)

箭头函数体内的this对象,就是定义该函数时所在定义域指向的对象,而不是使用时所在的作用域指向的对象

var name = 'window'; 

var A = {
   name: 'A',
   sayHello: () => {
      console.log(this.name)
   }
}

A.sayHello();// 还是以为输出A ? 错啦,其实输出的是window

由上面可知,箭头函数体内的this对象,就是定义该函数时所在定义域指向的对象 作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值