深入理解JavaScript中关键字this的使用

什么是this?

this是JavaScript的关键字之一。找拥有当前上下文(context)的对象(context object)。它是 对象 自动生成的一个内部对象,只能在 对象 内部使用。随着函数使用场合的不同,this的值会发生变化。this指向的是当前谁调用了它,它就指向谁。在普通情况下就是全局,浏览器里就是window;在use strict的情况下就是undefined。

function foo() {
    var a = 1;
    console.log(this.a);
}
var a = 10;
foo();  // 10,调用window.foo(),this指向window
function showThis() {
    console.log(this);
}

function showStrictThis() {
    'use strict'
    console.log(this);
}

showThis();  // window
showStrictThis();  // undefined

this指向什么,完全取决于 什么地方以什么方式调用,而不是 创建时。(比较多人误解的地方)(它非常语义化,this在英文中的含义就是 这,这个 ,但这其实起到了一定的误导作用,因为this并不是一成不变的,并不一定一直指向当前 这个)

var bar = {
    name: 'bar',
    returnThis() {
        return this;
    }
}

bar.returnThis() === bar;  // true
var bar = {
    name: 'bar',
    returnThis() {
        return this;
    }
}

var bar2 = {
    name: 'bar2',
    returnThis() {
        return bar.returnThis();
    }
}

var bar3 = {
    name: 'bar3',
    returnThis() {
        var returnThis = bar.returnThis;
        return returnThis();
    }
}

bar.returnThis();  // bar
bar2.returnThis();  // bar
bar3.returnThis();  // window

只要看使用this的那个函数。

在bar2.returnThis里,使用this的函数是bar.returnThis,所以this绑定到bar;

在bar3.returnThis里,使用this的函数是returnThis,所以this绑定到全局window。

function foo() {
    console.log(this.a);
}
var obj = {
    a: 10,
    foo: foo
};
foo();  // undefined
obj.foo();  // 10,函数foo执行的时候有了上下文对象,即 obj。这种情况下,函数里的this默认绑定为上下文对象,等价于打印obj.a,故输出10 。

如果是链性的关系,比如 xx.yy.obj.foo();, 上下文取函数的直接上级,即紧挨着的那个,或者说对象链的最后一个。
隐性绑定中有一个致命的限制,就是上下文必须包含我们的函数 ,例:var obj = { foo : foo },如果上下文不包含我们的函数用隐性绑定明显是要出错的,不可能每个对象都要加这个函数 ,那样的话扩展,维护性太差了,那怎么直接给函数强制性绑定this呢?利用Object.prototype.call和Object.prototype.apply,它们可以通过参数指改变this的指向,第一个参数都是 设置this对象。

两个函数的区别:
1. call从第二个参数开始所有的参数都是 原函数的参数。
2. apply只接受两个参数,且第二个参数必须是数组,这个数组代表原函数的参数列表。

function foo(a, b) {
    console.log(a + b);
}
foo.call(null, '你', '好');  // 你好
foo.apply(null, ['你', '好']);  // 你好

还有一个改变this指向的函数Object.prototype.bind,它不会立刻执行,只是将一个值绑定到函数的this上,并将绑定好的函数返回。它不但通过一个新函数来提供永久的绑定,还会覆盖call,apply的绑定

function returnThis() {
    return this;
}

var test01 = {name: 'test01'};
var test01returnThis = returnThis.bind(test01);
test01returnThis();  // test01
var test02 = {name: 'test02'};
test01returnThis.call(test02);  // test01
function foo() {
    console.log(this.a);
}
var obj = {a: 10};
foo = foo.bind(obj);
foo();  // 10
var obj2 = {a: 20};
foo.call(obj2);  // 10

当我们new一个函数时,就会自动把this绑定在新对象上,然后再调用这个函数。它会覆盖bind的绑定。使用new调用函数后,函数会 以自己的名字 命名 和 创建 一个新的对象,并返回。
创建一个新对象少不了一个概念,那就是构造函数,传统的面向对象 构造函数 是类里的一种特殊函数,要创建对象时使用new 类名()的形式去调用类中的构造函数,而js中就不一样了。
js中的只要用new修饰的 函数就是’构造函数’,准确来说是 函数的构造调用,因为在js中并不存在所谓的’构造函数’。
那么用new 做到函数的构造调用后,js帮我们做了什么工作呢?
1.创建一个新对象。
2.把这个新对象的__proto__属性指向 原函数的prototype属性。(即继承原函数的原型)
3.将这个新对象绑定到 此函数的this上 。
4.返回新对象,如果这个函数没有返回其他对象。

function foo() {
    this.a = 10;
    console.log(this);
}
foo();  // window
console.log(window.a);  // 10
var obj = new foo();  // foo {a: 10}
console.log(obj.a);  // 10

如果原函数返回一个对象类型,那么将无法返回新对象,你将丢失绑定this的新对象。

function foo() {
    this.a = 10;
    return new String('eeee');
}

var foo = new foo();
console.log(foo.a);  // undefined
console.log(foo);  // 'eeee'

this绑定的优先级
new 绑定 > 显示绑定 (bind、call、apply)> 隐式绑定 > 默认绑定(this 指向就是 window.(严格模式下默认绑定到undefined))

综合案例

function foo() {
    getName = function() {
        console.log(1);
    }
    // 这里的getName 将创建到全局window上
    return this;
}

foo.getName = function() {
    console.log(2);
};  // 这个getName和上面的不同,是直接添加到foo上的

foo.prototype.getName = function() {
    console.log(3);
};  //这个getName直接添加到foo的原型上,在用new创建新对象时将直接添加到新对象上

var getName = function() {
    console.log(4);
};  //和foo函数里的getName一样, 将创建到全局window上

function getName() {
    console.log(5);
}
// 同上,但是这个函数不会被使用,因为函数声明的提升优先级最高,所以上面的函数表达式将永远替换。这个同名函数,除非在函数表达式赋值前去调用getName(),但是在本题中,函数调用都在函数表达式。之后,所以这个函数可以忽略了

foo.getName();  // 2
getName();  // 5
foo().getName ();  // 1
getName();  // 1 

new foo.getName();  // 2。new 对一个函数进行构造调用 , 即 foo.getName ,构造调用也是调用啊。该执行还是执行,然后返回一个新对象,输出 2 (虽然这里没有接收新创建的对象但是我们可以猜到,是一个函数名为 foo.getName 的对象,且__proto__属性里有一个getName函数,是上面设置的 3 函数)

new foo().getName();  // 3。new 是对一个函数进行构造调用,它直接找到了离它最近的函数,foo(),并返回了应该新对象,等价于 var obj = new foo();obj.getName(); 这样就很清晰了,输出的是之前绑定到prototype上的那个getName  3 ,因为使用new后会将函数的prototype继承给 新对象

new new foo().getName();  //3。var obj = new foo();
                              var obj1 = new obj.getName();
好了,仔细看看, 这不就是上两题的合体吗,obj 有getName 3, 即输出3。
obj 是一个函数名为 foo的对象,obj1是一个函数名为obj.getName的对象

this指向中最强大的就是ES2016(ES6)中 的箭头函数 => 。箭头函数里的this永远指向当前词法作用域之中,称作 Lexical this ,在代码运行前就可以确定。没有其它函数或方法可以覆盖。对于箭头函数,只要看它在哪里创建的就行。
这样的好处就是方便让回调函数的this使用当前的作用域,不怕引起混淆。

判断this

现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的
顺序来进行判断:
1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo()
2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
var bar = foo.call(obj2)
3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。
var bar = obj1.foo()
4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。
var bar = foo()
就是这样。对于正常的函数调用来说,理解了这些知识你就可以明白 this 的绑定原理了。
不过……凡事总有例外。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值