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

时间:2021-11-05 18:50:36

什么是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(); // 4
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使用当前的作用域,不怕引起混淆。