面试常见问题,特地记录一下。
代码已经关联到github: 链接地址 觉得不错可以顺手点个star,这里会持续分享自己的开发经验(:
this
在 JavaScript 中,this
是指当前函数中正在执行的上下文环境,注意这里的上下文跟执行上下文不是一个概念,需注意区分。
下面从定义的方式和调用的方式上简单聊下其中的this指向问题。
不同定义的形式
function关键字
通过 function
构造的函数,非显式或者隐式简单调用函数时,也就是正常通过fn.()
去执行时,非严格模式会被绑定到全局对象window
上 ,严格模式下会被绑定到undefined
上面,其他不同调用方式下一节会细讲。
var obj = {
foo: function () {
//定义时位于全局,之后 obj 的 foo 指向了这个函数
console.log(this===window)
}
};
// 写法一 这种是方法调用
obj.foo() //false 通过obj 找到foo ,运行再obj内部 this==obj
// 写法二
var foo = obj.foo;
foo() // true foo指向函数本身 ,运行在全局, this==window 严格模式下为undefiend
箭头函数
箭头函数
是没有this
的,所以通过 箭头函数
构造的函数:
- 会始终跟随他外层函数或者全局的
this
,如果第一个嵌套的外层不存在this
,会再往上直至找到this
,最终直到全局。 - 使用
call
和apply
会忽略掉this
参数,就算是下文的其他调用方式箭头函数
也还是会依赖上一条的方式去查找this
,想改变箭头函数
的this
只能通过改变其父级的this
实现。
this
is the enclosing context where the arrow function is defined
var obj = {
foo: ()=> {
console.log(this === window)
}
};
// 写法一
//函数运行时,找到其父的执行上下文,所以这个this===window
obj.foo() // true
// 写法二
//函数运行时,找到其父的执行上下文,所以这个this===window
var foo = obj.foo;
foo() // true
//-----分割线
var obj2 = {
obj:function(){
return {
foo: ()=> {
console.log(this)//定义是外部的this,所以为obj2
}
};
}
}
var foo = obj2.obj().foo;
// 写法一
//函数运行时,找到其父的执行上下文,所以这个this===window
obj2.obj().foo() // this==window
// 写法二
//函数运行时,找到其父的执行上下文,所以这个this===window
foo() //this==window
不同的调用方式
一般函数的调用方式分四种:
- 作为函数直接被调用
parseInt('11')
- 作为对象的方法调用
console.log('Hello World!')
- 作为构造函数的实例调用
new RegExp('\\d')
- 间接调用
alert.call(undefined, 'Hello World')
函数直接调用
this
is the global object in a function invocation
全局对象取决于当前执行环境,在浏览器中,全局对象即 window
(严格模式下指向 undefiend
) 。
this.a = 1
this.b = 2
function sum(){
console.log(this === window)
console.log(this.a+this.b)
}
sum() //true 3 ,严格模式下: false 报错
对象方法调用
this
is the object that owns the method in a method invocation
当在一个对象里调用方法时,this
代表的是对象它自身。
const cat = {
type:'Cat',
legs:4,
logInfo : function() {
console.log(this)
console.log(this === cat); // => 1.true 2.false
console.log('The ' + this.type + ' has ' + this.legs + ' legs');
}
}
//1. The Cat has 4 legs
cat.logInfo()
//2. 打印出 "The undefined has undefined legs"
// 或者在严格模式下抛出错误 TypeError
//这里将方法函数传入延迟执行,延迟执行将其作为 函数调用
setTimeout(cat.logInfo, 1000);
class调用的也是他自身
class Planet {
constructor(name) {
this.name = name;
}
getName() {
console.log(this === earth); // => true
console.log(this.name); // => 'Earth'
return this.name;
}
}
var earth = new Planet('Earth');
//1. 作为方法调用,上下文为 earth
earth.getName(); // => true 'Earth'
//2.作为函数调用
var getName = earth.getName
getName(); // => false undefiend或者报错
构造函数调用
this
is the newly created object in a constructor invocation
构造器调用的环境是新创建的对象。通过传递构造函数参数来初始化新建的对象,添加属性初始化值以及事件处理器。
function Foo () {
console.log(this === Foo); // => 1.false 2.false
console.log(this instanceof Foo); // => 1.true 2.false 是window
this.property = 'Default Value';
//return this 2. 需要return this 否则为 undefined
}
//1. 构造函数调用
var fooInstance = new Foo();
console.log(fooInstance.property); // => 'Default Value'
//2. 忘记 new 关键字构造函数调用
var fooIns2 = Foo()
console.log(fooIns2.property); // => Cannot read property 'property' of undefined
指定this调用
间接调用表现为当一个函数使用了 .call()
或者 .apply()
方法。
this
is the first argument of.call()
or.apply()
in an indirect invocation
在间接调用中,this
指向的是 .call()
和 .apply()
传递的第一个参数。
const cat = {
type:'Cat',
legs:4,
}
function say(str){
console.log(str + ' say: ' + this.type + ' has ' + this.legs + ' legs')
}
// 间接调用
say.call(cat, 'Hello '); // => 'Hello say: Cat has 4 legs'
say.apply(cat, ['Bye ']); // => 'Bye say: Cat has 4 legs'
绑定this调用
绑定函数调用是将函数绑定一个对象,它是一个原始函数使用了 .bind()
方法。
this
is the first argument of.bind()
when invoking a bound function
绑定函数的 this
永远指向绑定时的上下文,间接调用也不能更改
const cat = {
type:'Cat',
legs:4,
}
const fish = {
type:'Fish',
legs:0,
}
function say(str){
console.log(str + ' say: ' + this.type + ' has ' + this.legs + ' legs')
}
const _say = say.bind(fish)
_say('Hello') //Hello say: Fish has 0 legs
// 间接调用
_say.call(cat, 'Bye '); // => Bye say: Fish has 0 legs
setTimeout调用
在《JavaScript高级程序设计》中有这样的描述:
setTimeout调用的代码都是在全局作用域中执行的,因此函数中的this的值在非严格模式下指向window对象,在严格模式下是undefined。
实时真的如此吗?我们看个例子:
//注意var 会绑定到window.a
var a = 'global'
function fn1(){
setTimeout(function(){
console.log('fn1', this.a)
})
}
function fn2(){
setTimeout(()=>{
console.log('fn2', this.a)
})
}
fn1()//global
fn2()//global
fn1.call({a:'obj'}) //global
fn2.call({a:'obj'}) // 'obj'
从上面的例子我们可以看到,对于function
定义的函数,确实跟红宝书介绍的一致,但是我们的箭头函数
比较特立独行,它依然会孜孜不倦的查找父级的this
。
总结
- 显式简单调用函数时,
- 严格模式下会被绑定到
undefined
上面 - 非严格模式会被绑定到全局对象
window
上
- 严格模式下会被绑定到
- 使用
new
方法调用构造函数,内部的this
会被绑定到新创建的对象 - 使用
call/apply/bind
方法调用函数,会指向传进去的参数对象 - 一般使用具体对象去调用函数,
this
会绑定到该对象 - 在
箭头函数
中,this
指向是由外层(函数或者全局作用域)决定的 setTimeout
调用函数都是在全局作用域中执行的,this
的绑定与调用与第1点一致,但是碰到箭头函数
时例外,以箭头函数
的指向为准。
实战1:
var numbers = {
numberA: 5,
numberB: 10,
sum: function() {
//函数关键字定义的函数,这里通过number调用,所以此处this为number
console.log(this === numbers); // => true
function calculate() {
// 注意这里的执行上下文环境是number.sum方法执行的上下文,也就是window
// this 是 window or undefined(严格模式下)
console.log(this === numbers); // => false
//window不存在这两个参数 返回NaN
return this.numberA + this.numberB;
}
return calculate();
},
sum2: ()=>{
//箭头函数只看执行时父级的上下文, numbers.sum2的执行上下文为window, 所以此处为window
console.log(this === numbers); // => false
function Calculate() {
// new 的情况下 this 是自身实例
console.log(this === numbers); // => false
return {
//自身实例不存在这两个参数 返回NaN
count: this.numberA + this.numberB
}
}
//new 构造函数调用
const c = new Calculate()
return c.count;
}
};
numbers.sum(); // true false => NaN , 严格模式下 throws TypeError
numbers.sum2() // false false => NaN , 严格模式下 throws TypeError