面试this问题——不翻车指南
① this指针
首先,想要搞懂指向问题,首先要知道this指针是什么,它存在的意义是什么。
this指针是什么?
可以认为this是当前函数运行环境的上下文。它是一个指针型的变量,我们把它理解成一个动态的对象,这个动态的对象就是当前运行环境的上下文。
this指针存在的意义?
复用!
this的出现就是为了提高函数的复用性,调用函数时可以使用不同的上下文:用不同的this调用同一个函数,可以产生不同的结果,就像面向对象编程中的多态 思想。
那么问题来了,既然这个this指针是一个动态的变量,我们在调用函数的时候,要根据什么规则来判定它的指向?
this指针的调用规则
普通函数(箭头函数另讲)在调用时遵循以下几种规则:
- 默认绑定
- 隐式绑定
- 显式绑定
- new绑定
① 默认绑定:函数独立调用,不带任何修饰的函数引用 让我们先看一道经典的面试题。
var name = 'Jack'
var obj = {
name : 'Tom',
foo : function(){
console.log(this.name)
}
}
var bar = obj.foo
bar()
在浏览器的严格模式下会输出:Uncaught TypeError
在浏览器非严格模式下会输出:Jack
当函数被独立调用时,默认绑定就会生效。在这个例子中,函数是在全局环境中被调用的,所以this会指向全局对象,而严格模式中不允许this指向全局对象。
默认绑定中,有一个点需要注意,当函数作为参数传递时,如setTimeout,setInterval,非严格模式下this依旧指向全局对象。
var name = 'Jack'
var obj = {
name: 'Tom',
foo: function () {
setTimeout(function () {
console.log(this.name)
},1000)
}
}
obj.foo()
依旧输出:Jack
② 隐式绑定:函数在调用时用到了修饰 比如下面这段函数被调用时,是由对象发起的,则this指向该对象。
var name = 'Jack'
var obj = {
name: 'Tom',
foo: function () {
console.log(this.name)
}
}
obj.foo()
当遇到隐式绑定的链式调用时,遵循就近原则。
该原则指的是:距离函数被调用的最近一个对象,为this的指向。
看一个例子:
var name = 'Jack'
var person1 = {
name: 'Tom',
foo: function () {
console.log(this.name)
}
}
var person2 = {
name: 'Harry',
friend:person1,
}
person2.friend.foo()
根据就近原则,我们很容易的就推出了会输出Tom。
因为在这个例子中,调用foo()的是person2.friend,那么this就指向了person1,打印的name就是person1的name。
③ 显式绑定:通过bind/apply/call改变指向 所有函数都可以用到bind/apply/call是因为这三个方法是挂载在Function原型下。
call和apply都是改变this指向后立即执行,它们的差别就是接收参数的类型不同:call函数接收的是?个参数列表,apply函数接收的是?个参数数组。
func.call(this, arg1, arg2, ...)
func.apply(this, [arg1, arg2, ...])
【面试this问题——不翻车指南】bind接收参数列表,且在改变this指向后,返回一个新函数,并不会立即执行。
(在非严格模式下,如果第一个参数this不传,则默认绑定到全局对象上。)
var person = {
name:'Jack'
}
function addInfo(age,work) {
this.age = age;
this.work = work
}
addInfo.apply(person,['25','HR'])
addInfo.call(person, '28', '前端')
console.log(person.age)
console.log(person.work)
如果在调用显式绑定的方法时,我们传入的this是一个number或者string,那么这个方法会把它转换为对象。
function getType() {
console.log(this,typeof this)
}
getType.apply('hi')
getType.apply(123)
输出:
[String: 'hi'] object
[Number: 123] object
④ new绑定 new的时候具体做了什么:
- 创建一个空对象
- 将空对象的proto指向原对象的prototype
- 执行构造函数中的方法
- 返回这个新对象
function study(name){
this.name = name;
}
var obj = new study('Jack')
console.log(obj.name)
根据new绑定>显式绑定>隐式绑定>默认绑定的规则可以试着看一下接下来的例子会返回什么?
function foo() {
console.log(this.a) }
var obj1 = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: foo
}
obj1.foo();
obj2.foo();
obj1.foo.call(obj2);
obj2.foo.call(obj1);
根据显示绑定>隐式绑定的规则,输出:1 2 2 1
接下来难度升级
function foo(something) {
this.a = something
}
var obj1 = {
foo: foo
}
var obj2 = {}
obj1.foo(2);
console.log(obj1.a);
obj1.foo.call(obj2, 3);
console.log(obj2.a);
var bar = new obj1.foo(4);
console.log(obj1.a);
console.log(bar.a);
输出 2 3 2 4。
首先第一个是输出obj1.a的值,显然obj1.foo(2)是隐式调用,this指向obj1,所以obj1中的a的值为2。
obj2.a的值通过显式绑定的方法赋值的,显式会大于隐式优先级,所以obj2.a的值是3。
当new obj1.foo(4)时,本质是创建了一个新的对象,且把这个新对象返回了出去,所以对obj1本身并没有什么影响。obj1.a的值依旧是2。
bar的值是一个被new出来的新对象,a的值为4。
再练一道:
function foo(something) {
this.a = something
}
var obj1 = { }
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);
var baz = new bar(3);
console.log(obj1.a);
console.log(baz.a);
输出:2 2 3
⑤ 箭头函数 以上讲的四种绑定方式都是针对普通函数来说的,ES6引进的箭头函数则不受前面规则的约束,它比较特殊,需要单独来分析。
普通函数的this指向都是在调用的时候决定的,而箭头函数的指向是在定义的时候就决定了。
它有以下几个特征:
- 箭头函数没有 arguments
- 箭头函数没有构造函数
- 箭头函数没有原型对象
- 箭头函数没有自己的this
var name = '123';
var obj = {
name: '456',
print: function () {
function a() {
console.log(this.name);
}
a();
}
}
obj.print();
// 输出123
function Foo() {
Foo.a = function () {
console.log(1);
}
this.a = function () {
console.log(2)
}
}
Foo.prototype.a = function () {
console.log(3);
}
Foo.a = function () {
console.log(4);
}
Foo.a();
// 4
let obj = new Foo();
obj.a();
//2
Foo.a();
//1
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
//10
arguments[0]();
//2
}
};
obj.method(fn, 1);
推荐阅读
- parallels|parallels desktop 解决网络初始化失败问题
- jhipster|jhipster 升级无效问题
- “精神病患者”的角度问题
- 解决SpringBoot引用别的模块无法注入的问题
- Hive常见问题汇总
- 姚老师互动问答会|姚老师互动问答会 # 问题001(如何更有智慧的和身边人分享金刚智慧())
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- 【教育故事】|【教育故事】 一个“问题学生”的蜕变
- 蓝桥杯试题
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片