一篇文章让你搞定JavaScript的this指向问题

图片

该文章配有我的的视频讲解,但愿看文章的同窗,能够看文章,文章看不太明白的,结合视频讲解瞬间明白。《深刻学习JavaScript的this指向》
javascript


若是要问javascript中哪两个知识点容易混淆,做用域查询和this机制绝对名列前茅。前面章节已经详细介绍过做用域的知识。本章将介绍this机制。java

this绑定规则

默认绑定

全局环境下,this默认绑定到window数组

console.log(this === window); //true

函数独立调用时,this默认绑定到window闭包

function foo(){    console.log(this === window);}foo();//true

被嵌套的函数独立调用时,this默认绑定到windowapp

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

上面代码虽然test()函数被嵌套在obj.foo()函数中,但test()函数是独立调用,而不是方法调用。因此this默认绑定到windowide

IIFE函数

IIFE当即执行函数实际是函数声明后当即调用执行,内部的this指向了window学习

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

等价于上例ui

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

闭包this

相似地,test()函数是独立调用,而不是方法调用,因此this默认绑定到window

注意:函数共有4中调用方法

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

因为闭包的this默认绑定到window对象,但又经常须要访问嵌套函数的this,因此经常在嵌套函数中使用var that = this,而后在闭包中使用that替代this,使用做用域查找的方法来找到嵌套函数的this值

var a = 0;function foo(){    var that = this;    function test(){        console.log(that.a);    }    return test;};var obj = {    a : 2,    foo:foo}obj.foo()();//2

隐式绑定

通常地,被直接对象所包含的函数调用,也被称为方法地调用,this隐式绑定到该直接对象

function foo(){    console.log(this.a);}var obj1 = {    a : 1;    foo: foo,    obj2 : {        a:2,        foo:foo    }}//foo()函数的直接对象是obj1,this隐式绑定到obj1obj1.foo();//1//foo()函数的直接对象是obj2,this隐式绑定到obj2obj1.obj2.foo();//2

隐式丢失

隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。这种状况容易出错却又常见

函数别名

var a = 0;function foo(){    console.log(this.a);}var obj = {    a: 1,    foo:foo}//把obj.foo赋予别名bar,形成了隐式丢失,由于只是把foo()函数赋给了bar,而bar与obj对象则毫无关系var bar = obj.foo;bar();//0
//等价于var a = 0;var bar = function foo(){    console.log(this.a);}bar();//0

【参数传递】

var a = 0;
function foo() {    console.log(this.a);}
function bar(fn) {    fn();}var obj = {    a: 2,    foo: foo}//把obj.foo当作参数传递给bar函数时,有隐式的函数赋值 fn = obj.foo,只是把foo函数赋给了fn,而fn与obj对象毫无关系bar(obj.foo);//0
//等价于var a = 0;function bar(fn){    fn();}bar(function foo(){    console.log(this.a);})

【内置函数】

内置函数与上例相似,也会形成隐式丢失

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

【间接调用】

函数的“间接引用”通常都在无心间建立,最容易在赋值时发生,会形成隐式丢失

function foo(){    console.log(this.a);}var a = 2;var o = {a: 3,foo: foo};var p = {a: 4};o.foo();//3;//将o.foo函数赋值给p.foo函数,而后当即执行。至关于仅仅是foo()函数的当即调用(p.foo = o.foo)();//2
//另外一种状况function foo() {    console.log( this.a );}var a = 2;var o = { a: 3, foo: foo };var p = { a: 4 };o.foo(); // 3//将o.foo函数赋值给p.foo函数,以后p.foo函数再执行,是属于p对象的foo函数的执行p.foo = o.foo;p.foo();//4

【其余状况】


在javascript引擎内部,obj和obj.foo储存在两个内存地址,简称为M1和M2。只有obj.foo()这样调用时,是从M1调用M2,所以this指向obj。可是,下面三种状况,都是直接取出M2进行运算,而后就在全局环境执行运算结果(仍是M2),所以this指向全局环境


var a = 0;var obj = {    a:2,    foo:foo}function foo(){    console.log(this.a);}(obj.foo = obj.foo)();//0(false || obj.foo)();//0(1,obj.foo)();//0

显示绑定

经过call()、apply()、bind()方法把对象绑定到this上,叫作显示绑定。对于被调用的函数来讲,叫作间接调用

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

普通的显示绑定没法解决隐式丢失问题

var a = 0;function foo(){    console.log(this.a);}var obj1 = {    a : 1};var obj2 = {    a : 2}foo.call(obj1);//1foo.call(obj2);//2

【硬绑定】

硬绑定是显式绑定的一个变种,使this不能再被修改

var a = 0;function foo(){    console.log(this.a);}var obj = {    a:2};var bar = function (){    foo.call(obj);}//在bar函数内部手动调用foo.call(obj)。所以,不管以后如何调用函数bar,它总会手动会在obj上调用foobar();//2setTimeout(bar,2000);//2bar.call(window);//2

【API】

javascript中新增了许多内置函数,具备显式绑定的功能,如数组的5个迭代方法:map()forEach()filter()some()every()

var id = 'window';function foo(el){    console.log(el,this.id);}var obj = {    id: 'fn'};[1,2,3].forEach(foo);//1 "window" 2 "window" 3 "window"[1,2,3].forEach(foo,obj);//1 "fn" 2 "fn" 3 "fn"

new绑定


若是函数或者方法调用以前带有关键字new,它就构成构造函数调用。对于this绑定来讲,称为new绑定


【1】构造函数一般不适用return关键字,他们一般初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种状况下,构造函数调用表达式的计算结果就是这个新对象的值


function fn(){    this.a = 2;}var test = new fn();console.log(test);//{a:2}

【2】若是构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象做为调用结果

function fn(){    this.a = 2;    return;}var test = new fn();console.log(test);//{a:2}

【3】使用构造函数显示地使用return语句返回一个对象,那么调用表达式的值就是这个对象

var obj = {a:1};function fn(){    this.a = 2;    return obj;}var test = new fn();console.log(test);//{a:1}

注意:尽管有时候构造函数看起来像一个方法调用,它依然会使用这个新对象做为this。也就是说,在表达式new o.m()中,this并非o


var o = {    m:function(){        return this;    }}var obj = new o.m();console.log(obj,obj === o);//{} falseconsole.log(obj.contructor === o.m);//true

严格模式

【1】严格模式下,独立调用的函数的this指向undefined

function fn(){    'use strict';    console.log(this);//undefined}fn();
function fn(){    console.log(this);//window}fn();

【2】在非严格模式下,使用函数的call()或apply()方法时,null或undefined值会被转换成全局对象。而在严格模式下,函数的this值始终是指定的值

var color = 'red';function displayColor(){    console.log(this.color);}displayColor.call(null);//red
var color = 'red';function displayColor(){    'use strict';    console.log(this.color);}displayColor.call(null);//TypeError: Cannot read property 'color' of null

总结

  • this的四种绑定规则:默认绑定、隐式绑定、显式绑定和new绑定,分别对应函数的四种调用方式:独立调用、方法调用、间接调用和构造函数调用。

  • 分清这四种绑定规则不算难,可是比较麻烦的是须要练就火眼金睛,识别出隐式丢失的状况。

  • 说到底,JavaScript如此复杂的缘由是由于函数过于强大。由于,函数是对象,因此原型链比较复杂;由于函数能够做为值被传递,因此执行环境栈比较复杂;一样地,由于函数具备多种调用方式,因此this的绑定规则也比较复杂

  • 只有理解了函数,才算理解javascript

相关文章
相关标签/搜索