参考链接:
一道JS面试题所引发的"血案",透过现象寻本质,再从本质看现象
JavaScript This 之谜
在 JavaScript 中,this 是指当前函数中正在执行的上下文环境:
- 函数调用
- 方法调用
- 构造函数调用
- 间接调用
- 绑定函数调用
- 箭头函数
- 易错场景
函数调用
函数调用 代表了该函数接收以成对的引号包含,用逗号分隔的不同参数组成的表达式。
函数调用中的this
function sum(a, b) {
console.log(this === window); // => true
this.myNumber = 20; // 在全局对象中添加 'myNumber' 属性
return a + b;
}
// sum() 为函数调用
// this 在 sum() 中是全局对象 (window)
sum(15, 16); // => 31
window.myNumber; // => 20
当 sum(15, 16) 被调用时,JavaScript 自动将 this 设置为全局对象,即 window。
严格模式下,函数调用中的 this
严格模式由 ECMAScript 5.1 引进,用来限制 JavaScript 的一些异常处理,提供更好的安全性和更强壮的错误检查机制。使用严格模式,只需要将 'use strict' 置于函数体的顶部。这样就可以将上下文环境中的 this 转为 undefined。这样执行上下文环境不再是全局对象,与非严格模式刚好相反。
function multiply(a, b) {
'use strict'; // 开启严格模式
console.log(this === undefined); // => true
return a * b;
}
// 严格模式下的函数调用 multiply()
// this 在 multiply() 中为 undefined
multiply(2, 5); // => 10
严格模式不仅在当前作用域起到作用,它还会影响内部作用域,即内部声明的一切内部函数的作用域。
function execute() {
'use strict'; // 开启严格模式
function concat(str1, str2) {
// 内部函数也是严格模式
console.log(this === undefined); // => true
return str1 + str2;
}
// 在严格模式下调用 concat()
// this 在 concat() 下是 undefined
concat('Hello', ' World!'); // => "Hello World!"
}
execute();
方法调用
当在一个对象里调用方法时,this 代表的是对象它自身。
严格模式不仅在当前作用域起到作用,它还会影响内部作用域,即内部声明的一切内部函数的作用域。
var calc = {
num: 0,
increment: function() {
console.log(this === calc); // => true
this.num += 1;
return this.num;
}
};
// 方法调用,this 指向 calc
calc.increment(); // => 1
calc.increment(); // => 2
构造函数调用
function Foo () {
console.log(this instanceof Foo); // => true
this.property = 'Default Value';
}
// 构造函数调用
var fooInstance = new Foo();
fooInstance.property; // => 'Default Value'
间接调用
间接调用表现为当一个函数使用了 .call() 或者 .apply() 方法。.call() 和 .apply() 被用来配置当前调用的上下文环境。
var rabbit = { name: 'White Rabbit' };
function concatName(string) {
console.log(this === rabbit); // => true
return string + this.name;
}
// 间接调用
concatName.call(rabbit, 'Hello '); // => 'Hello White Rabbit'
concatName.apply(rabbit, ['Bye ']); // => 'Bye White Rabbit'
两个方法最主要的区别为 .call() 接收一组参数,而 .apply() 接收一串参数作为类数组对象传递。
绑定函数调用
绑定函数调用是将函数绑定一个对象,它是一个原始函数使用了 .bind() 方法。
方法 .bind(thisArg[, arg1[, arg2[, ...]]]) 接收第一个参数 thisArg 作为绑定函数在执行时的上下文环境,以及一组参数 arg1, arg2, ... 作为传参传入函数中。 它返回一个新的函数,绑定了 thisArg。
function multiply(number) {
'use strict';
return this * number;
}
// 创建绑定函数,绑定上下文2
var double = multiply.bind(2);
// 调用间接调用
double(3); // => 6
double(10); // => 20
var numbers = {
array: [3, 5, 10],
getNumbers: function() {
return this.array;
}
};
// 创建一个绑定函数
var boundGetNumbers = numbers.getNumbers.bind(numbers);
boundGetNumbers(); // => [3, 5, 10]
// 从对象中抽取方法
var simpleGetNumbers = numbers.getNumbers;
simpleGetNumbers(); // => undefined 或者严格模式下抛出错误
.bind() 创建了一个永恒的上下文链并不可修改。一个绑定函数即使使用 .call() 或者 .apply()传入其他不同的上下文环境,也不会更改它之前连接的上下文环境,重新绑定也不会起任何作用。
function getThis() {
'use strict';
return this;
}
var one = getThis.bind(1);
// 绑定函数调用
one(); // => 1
// 使用 .apply() 和 .call() 绑定函数
one.call(2); // => 1
one.apply(2); // => 1
// 重新绑定
one.bind(2)(); // => 1
// 利用构造器方式调用绑定函数
new one(); // => Object
补充bind用法
bind()的另一个最简单的用法是使一个函数拥有预设的初始参数。这些参数(如果有的话)作为bind()的第二个参数跟在this(或其他对象)后面,之后它们会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们的后面。
function list() {
return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
箭头函数
箭头函数的设计意图是以精简的方式创建函数,并绑定定义时的上下文环境。
箭头函数并不创建它自身执行的上下文,使得 this 取决于它在定义时的外部函数。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
log() {
console.log(this === myPoint); // => true
setTimeout(()=> {
console.log(this === myPoint); // => true
console.log(this.x + ':' + this.y); // => '95:165'
}, 1000);
}
}
var myPoint = new Point(95, 165);
myPoint.log();
箭头函数一次绑定上下文后便不可更改,即使使用了上下文更改的方法.
var numbers = [1, 2];
(function() {
var get = () => {
console.log(this === numbers); // => true
return this;
};
console.log(this === numbers); // => true
get(); // => [1, 2]
// 箭头函数使用 .apply() 和 .call()
get.call([0]); // => [1, 2]
get.apply([0]); // => [1, 2]
// Bind
get.bind([0])(); // => [1, 2]
}).call(numbers);
易错场景
this 在内部函数中
var numbers = {
numberA: 5,
numberB: 10,
sum: function() {
console.log(this === numbers); // => true
function calculate() {
console.log(this === numbers); // => false
return this.numberA + this.numberB;
}
return calculate();
}
};
numbers.sum();
解决办法:return calculate.call(this);
this在回调函数中
function Animal(type, legs) {
this.type = type;
this.legs = legs;
this.logInfo = function() {
console.log(this === myCat); // => false
console.log('The ' + this.type + ' has ' + this.legs + ' legs');
}
}
var myCat = new Animal('Cat', 4);
setTimeout(myCat.logInfo, 1000);
解决办法:setTimeout(myCat.logInfo.bind(myCat), 1000);
把包含this的方法赋给一个变量
var name = "aa";
var user = {
name: 'hhh',
sayName: function(){
console.log(this.name);
}
}
var test = user.sayName;
test(); // aa
解决办法:var test = user.sayName.bind(user);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。