函数内部:this详解(重点)
文章目录
一、引入this
this是JavaScript的关键字之一,作为函数内部的一个特殊对象,我们通常所说的this值指的是把包含它的函数被当作方法调用时的上下文对象。
听着有点绕口,于是我简单粗暴地将上面这句话拆分成三大块,便于理解:
- this是函数内部的一个对象(类似于arguments)
- 当该函数被xxx对象调用时
- this的值就是xxx对象
二、this绑定规则
当你理解了上文概括的三句话后,此时this已经向你打开了一扇门!以下我们将通过多种多样的栗子,了解this的各类令人作呕的操作
1.默认绑定
直接不带任何修饰的赤裸裸的函数调用,this将默认绑定,一般是绑定到window象上(当函数在全局上下文中调用)
注意:在严格模式下,调用函数时如果没有指定上下文对象,this值不会指向window,除非使用apply()或call()把函数指定给一个对象,否则this值会变成undefined
举个栗子:
window.color = 'pink';
function sayColor() {
let color = 'blue';
console.log(this.color);
}
sayColor();
输出结果:pink
分析:根据上栏2、3,首先找到sayColor()的调用对象是谁,很明显sayColor()在全局上下文中被调用,调用对象为window对象,此时this的值就是window对象,this.color相当于window.color
2.隐性绑定
函数调用时有了自己的上下文对象,函数的this绑定该上下文对象上
举个栗子:
window.color = 'pink';
function sayColor() {
let color = 'blue';
console.log(this.color);
}
let obj = {
color: 'red',
sayColor: sayColor
};
obj.sayColor();
输出结果:red
分析:同理,找到sayColor()的调用对象是谁,显然是对象obj,此时this值就是对象obj,this.color相当于obj.color
补充:如果是链式的关系,如:xxx.obj.sayColor(),则采取就近原则,函数里的this默认绑定挨着最近的上下文对象
3.new绑定
在JavaScript机制中,当我们的函数通过new关键字修饰时候,该函数作为构造函数调用,通过new关键字创建构造函数时,JavaScript为我们做了以下工作:
- 创建一个新对象
- 把该对象的_proto_指向原函数的prototype属性(继承原函数的原型) -------后续文章介绍原型与原型链
- 该新对象上绑定到构造函数的this上
- 返回新对象,在该构造函数不返回其他对象的情况下
举个栗子:
window.name = "Matt";
function Person() {
this.name = "Nicholas";
this.age = 29;
this.job = "Software Engineer";
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
// 使用new操作符作为构造函数调用,该构造函数this绑定到对象person上
let person = new Person();
person.sayName(); // Nicholas
sayName(); // Matt
console.log(person.__proto__ === Person.prototype); // true
输出结果:Nicholas Matt
分析:构造函数Person通过new关键字创建对象person,该对象上绑定到构造函数的this上,同理,找到sayName()的调用对象是谁,显然是对象person,this.name相当于person.name也就是构造函数的name,为Nicholas;而在全局中直接调用sayName(),该函数的this值为window对象(默认绑定),打印Matt(此段需细品)
注意:如果该构造函数内部返回其他对象的情况下,将无法返回new关键字创建的新对象,将与this失去绑定(废话,对象都没了),结果将报错
举个栗子:
function Person() {
this.name = "Nicholas";
this.sayName = sayName;
return new String("哈哈哈你对象没啦!"); // 返回新对象
}
function sayName() {
console.log(this.name);
}
// 使用new操作符作为构造函数调用,该构造函数this绑定到对象person上
let person = new Person();
person.sayName(); // TypeError: person.sayName is not a function
4.call()、apply()、bind()显性绑定
隐性绑定具有一个致命的限制,就是其绑定对象上下文必须包含我们的函数,如上文中let obj = { sayColor: sayColor },如果未包含我们的函数隐性绑定将会报错,也就是对象无法调用函数使得函数的this值为该对象。不可能给每个对象都声明一个属性保存该函数,那样代码的可维护性太差,因此此处引出显性绑定,给函数强制性绑定this
apply()和call()真正强大的地方是控制函数上下文(即函数体内)this值的能力
举个栗子:
window.age = 29;
let a = {
age: 33
};
let b = {
age: 66
};
function sayAge() {
console.log(this.age);
}
sayAge(); // 29 this值默认为window对象
sayAge.call(a); // 33 将this值显性切换为对象a
sayAge.apply(b); // 66 将this值显性切换为对象b
原理:call()与apply()将任意对象设置为任意函数的作用域,如:此例通过sayAge.call(a)将对象a设置为函数sayAge()的作用域,因此sayAge()函数体中打印this.age将在对象a的上下文中去寻找age并打印,即打印33;sayAge.apply(b)同理
ES5同样定义了一个新方法bind(),跟call()和apply()的不同之处在于:它控制函数this值的同时,会创建一个新的函数(准确来说是改变新函数的this值)。
举个栗子:
window.age = 29;
let a = {
age: 33
};
function sayAge() {
console.log(this.age);
}
let sayAgeAgain = sayAge.bind(a); // 创建新函数sayAgeAgain
sayAgeAgain(); // 全局作用域中调用
分析:在sayAge()上调用bind()并传入对象a并且还创建了一个新函数sayAgeAgain()。此时sayAgeAgain()的this值被设置为对象a!!因此在全局作用域中调用sayAgeAgain(),也会返回33,this不会指向window。
三、箭头函数
原理:箭头函数会保留定义该函数时的上下文,
举个栗子:
window.color = 'red';
let obj = {
color: 'blue'
};
// 此时箭头函数sayColor()声明在全局上下文
let sayColor = () => console.log(this.color);
obj.sayColor = sayColor;
obj.sayColor(); // 依旧为red
输出结果:red
解析:本例中,箭头函数声明在全局上下文中,this值为window对象,我试图通过obj.sayColor = sayColor的形式隐性绑定将this值改为对象obj,再通过对象obj调用sayColor(),但输出仍然为window对象的color属性red,因为箭头函数会保留声明该函数时的上下文,我将它声明在了全局上下文中,因此其this值只能是window对象
总结
本章节深入浅出的介绍了JavaScript函数内部的this对象的各种行为,作为热门面试知识点,基于JavaScript高级程序设计(红宝书)案例+segmentfault的各类文章阐述结合自身理解总结所作,希望对新手小白对JavaScript向更高阶语法迈进会有一定帮助,有不足之处或错误愿意接受批评指正!!语言组织不易,希望亲带着我的理解细细品读,呜呜呜!