5.完全搞懂javascript-this

在第二章运行上下文(Execution Context)中咱们提升运行上下文的结构:java

function ExecutionContext() {
    this.LexicalEnvironment = undefined;
    this.VariableEnvironment =  undefined;
    this.ThisBinding = undefined;
}
复制代码

这篇就来聊聊这个ThisBinding。当前运行上下文的ThisBinding其实就是在当前运行上下文上执行代码里this的值。因此你在代码执行的时候遇到"this",就会来找当前运行上下文的这个ThisBinding做为this的值。那么,ThisBinding的值是多少呢?浏览器

要知道ThisBinding的值是多少,ExecutionContext何时被建立,建立时ThisBinding被设置为何。bash

而咱们又知道JS代码在三种状况下会建立ExecutionContext:app

可运行代码(Executable Code)

ECMAScript 5 规范,定义了三类可运行代码(Executable Code) ,运行这些代码时候会建立运行上下文(Execution Contexts):函数

  • global code:就是js整个“程序”,就是源代码文件中全部不在function体中的代码。
  • function code:就是函数体中的代码,除了内嵌函数体中的代码之外
  • eval code : 就是传给内置eval函数的代码字符串

只要咱们了解运行这三种代码时候,建立了ExecutionContext的ThisBinding被设置为多少。就知道运行在该运行上下文的this的值了。ui

global code中的this

这个咱们提过:this

当JS引擎开始要进行global code代码运行以前,会先建立一个全局运行上下文(global execution context),并放入运行栈中:spa

//建立一个空的运行上下文
var globalExecutionContext = new ExecutionContext();

//建立全局词法环境
GlobalEnvironment = creatGlobalEnvironment(globalobject)//能够看做是浏览器环境下的window

//设置运行上下文
globalExecutionContext.LexicalEnvironment = GlobalEnvironment;
globalExecutionContext.VariableEnvironment = GlobalEnvironment;
globalExecutionContext.ThisBinding = globalobject;

Runtime.push(globalExecutionContext);

//这时的Runtime是这样的:
Runtime = {
   executionContextStack: [globalExecutionContext];
};

复制代码

在进入程序代码以前,建立了全局运行上下文,其中的ThisBinding被设置为了全局对象:globalExecutionContext.ThisBinding = globalobject;prototype

因此在全局代码中的this的值为为全局对象(浏览器下为window)。code

函数中this

在function code里this的值就比较多变了,咱们在函数调用一篇中提到,用不一样的调用方式调用函数后,进入function code以前会设置不一样thisArg,并传递到function code代码中 。进入function code后,会建立function的运行上下文,且设置其ThisBinding为thisArg。所以函数里的this的值,就和调用关系又很大的关系。

这里强调一下,函数中的this和函数的调用方式相关而与在哪被建立无关。与函数的词法环境的"静态"相比它是动态的。它之和当前运行上下文的ThisBings相关。

咱们在函数运行讲过:

那这五种调用方式,在进入函数代码运行以前,携带进去的,要做为this的"东西"都是啥呢?

  1. 带undefined 进去的:函数调用functionName();和 当即调用函数表达式(function(){})(),(function functionName(){})();
  2. 带对象进去的:
    • 方法调用:如someObj.method() : 带someObj进去
    • new functionName() 方式的调用:建立一个新对象 newObject,带进去
    • functionName.call和functionName.apply:把call和apply指定thisArg带进去
  • 判断携带进来的thisArg的值:

    • 若是是strict,使barExecutionContext.ThisBinding = thisArg;
      • 不是strict
        • 若是thisArg是undefined,使barExecutionContext.ThisBinding = globalobject;
        • 若是thisArg不是undefined,使barExecutionContext.ThisBinding = toObject(thisArg);

所以函数中this的值就有几种状况:

普通函数调用

由于普通函数调用,包括调用functionName();和 当即调用函数表达式(function(){})(),(function functionName(){})();等,传到函数里的thisAarg是undefined。

所以,若是是非strict,则ThisBinding = globalobject;也就是:

  • 在非strict mode下,普通函数调用中this为全局对象
  • 在strict mode下,普通函数调用中this为undefined

方法调用

针对如someObj.method() : someObj做为 thisArg传进函数代码里,在建立函数运行上下文的时候,ThisBinding = someObj。

所以在方法调用模式someObj.method()中的this,为someObj。

var a = 2;
var someObj = {
    a:1
    print:function(){
        console.log(this.a)
    }
}

var outPrint = someObj.print;

someObj.print(); //1
outPrint();//2
复制代码

outPrint()调用时,传进函数的thisArg是undefined,所在outPrint()的运行上下文中,ThisBinding被设置为全局对象,因此这时,this.a就是全局对象上的var a = 2;

而someObj.print()调用时,传进传进函数的thisArg是someObj,因此在someObj.print()运行上下文中,ThisBinding被设置someObj,因此这时this.a就是someObj上a:1。

注意,函数(箭头函数除外)里的this只和调用方式相关和在哪调用,函数在哪建立无关。

new 调用

  • new functionName() 方式的调用:建立一个新对象 newObject,带进去

在new方式调用的函数运行上下文中,ThisBinding = newObject,所以在new方式调用的函数中,this值就是新建立的对象newObject。

function Point(x, y) {
    this.x = x;
    this.y = y;
}
var p = Point(7, 5); // 没有new,普通调用,this为全局对象,在全局对象上建立x=7 y=5
var point = new Point(7, 5); //使用new调用,this为新建立的对象,在新对象上建立x=7,y=5

console.log(x); // 7
console.log(y); // 5
复制代码

call,apply调用

call和apply调用,可以显示的传递给函数thisArg。其实不仅是Function.prototype.call和Function.prototype.apply这个两个函数能够给调用的函数显示传递thisArg参数,下列的方法都给函数调用显示传递提供thisArg:

  • Function.prototype.apply( thisArg, argArray )
  • Function.prototype.call( thisArg [ , arg1 [ , arg2, ... ] ] )
  • Function.prototype.bind( thisArg [ , arg1 [ , arg2, ... ] ] )
  • Array.prototype.every( callbackfn [ , thisArg ] )
  • Array.prototype.some( callbackfn [ , thisArg ] )
  • Array.prototype.forEach( callbackfn [ , thisArg ] )
  • Array.prototype.map( callbackfn [ , thisArg ] )
  • Array.prototype.filter( callbackfn [ , thisArg ] )

须要注意的是若是传递给thisArg的null或者undefined,在非严格模式ixia函数中this仍是全局对象,记得咱们提到过,进入到函数代码,建立运行上下文以前的判断吗:

  • 判断携带进来的thisArg的值:

    • 若是是strict,使barExecutionContext.ThisBinding = thisArg;
      • 不是strict
        • 若是thisArg是undefined,使barExecutionContext.ThisBinding = globalobject;
        • 若是thisArg不是undefined,使barExecutionContext.ThisBinding = toObject(thisArg);
var obj = {
    a:1
};

function print() {
    console.log(this);
}


print.call(null);//window
print.call(undefined);//window
print.call(obj);//obj
复制代码

bind

Function.prototype.bind,的只要功能是建立一个和原函数同样body和[[scope]]的函数,可是它的this则绑定为bind函数的第一个参数。在新函数中,不管函数如何被使用,this都会永久绑定到bind的第一个参数。

function f() {
  return this.a;
}

var g = f.bind({a: 'azerty'});
console.log(g()); // azerty

复制代码

箭头函数

箭头函数是ES6的新内容,这里讲到this,顺便提一下。 箭头函数的this保存为它被建立时运行上下文的this的值。在global下建立箭头函数,为箭头函数this为global,保持为它被建立时的this值。在function里建立也同样,它的this保持为在箭头函数被建立时function运行上下文的this的值。

这个和咱们前面提到的函数都不同,前面提到的函数都是和调用时的运行上下文相关,箭头函数的this却和建立时的运行上下文相关。

还有一点就是,对箭头函数调用call apply 和 bind 来绑定this是无效,没有效果。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true


var obj = {func: foo};
console.log(obj.func() === globalObject); // true

console.log(foo.call(obj) === globalObject); // true

foo = foo.bind(obj);
console.log(foo() === globalObject); // true
复制代码

事件绑定函数中this

分两种状况:

  • 做为event handler,addEventListener添加为元素的事件回调函数,其中this为触发该事件的元素对象(若是不是addEventListener添加,有些浏览器不遵循此规则)
  • inline event handler: this为监听函数所在的元素,只有最外层的this是这样,若是是是内层function里的this则为全局或者undefined
<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>

function bluify(e) {
    console.log(this);
}


var elements = document.getElementsByTagName('button');
elements[0].addEventListener('click', bluify, false);
复制代码

Eval Code 中的this

JS三种可运行代码,咱们已经提到过global、function,只上下eval还没提到。由于eval比较少用,咱们也不细究。这里大概讲下,进入eval代码之后,运行上下文的建立过程。

分两种状况:

  1. 直接调用,则运行上下文的建立过程至关于复制一份调用eval的那个运行上下文,在全局上直接调用eval,则复制一份全局运行上下文,在函数中直接运行eval,则复制函数的运行上下文,因此这个种状况下下,eval种的this和调用它的那个运行上下文的this相同。
eval("var definedInEval = 2;console.log(this);");//window 

console.log(definedInEval); //2 

var obj = {
    method: function () {
        eval('console.log(this)'); // obj
    }
}
obj.method(); 

复制代码
  1. 非直接调用,无论在哪调用,则其运行上下文都是复制全局运行上下文,因此this都是全局对象
var evalcopy = eval;

evalcopy("console.log(this);");

var obj = {
    method: function () {
        evalcopy('console.log(this)'); //window
    }
}
obj.method(); 
复制代码
相关文章
相关标签/搜索