this相关内容总结,比如如何正确判断 this?箭头函数的 this 是什么?
流程图【必须牢记】
图中的流程只针对于单个规则。
小结
- 首先,
new
的方式优先级最高,——var f = new foo()
,foo中的this指向f实例 - 接下来是
fn.bind()
这些函数, ——fn 中的 this 永远由第一次 bind 决定,如果bind第一个参数为空,那么就是 window - 然后是
obj.foo()
这种调用方式, —— foo中的this指向obj - 最后是
foo()
这种直接调用方式, ——不管 foo 函数被放在了什么地方,this 一定是 window - 同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。 ——箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this
- 立即执行函数表达式、
setTimeout
计时器等的回调函数若为普通函数,则函数中的this指向 window【需要查看书籍确认下】。若为箭头函数,则按箭头函数的规则来。
1. 函数调用
练习3-1
function foo() {
console.log(this.a)
}
var a = 1
foo()
const obj = {
a: 2,
foo: foo
}
obj.foo()
const c = new foo()
分析:
对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window;
对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象;
对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this。
练习3-2【好例子,interview】
var num = 1;
function foo() {
console.log(num,this.num);
console.log(cc)
}
function biz() {
var num = 2;
var cc = 3;
foo();
}
biz();
答案:1 1;报错:Uncaught ReferenceError: cc is not defined
我的分析:
由于foo函数是在biz函数内部直接被调用的,对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window,访问的也是全局环境,所以this.num值为1。
词法作用域链:
window
|—— num
|—— foo()
|—— biz()
|—— num
|—— cc
所以:foo()
执行时访问到的 num 其实是全局的num=1 访问的cc也是全局的,但是由于cc并未在全局环境声明,所以报错。注意:foo是无法访问到biz中声明的变量的!
2. 箭头函数【典例】
- 箭头函数里其实是没有
this
、arguments
的,箭头函数中的 this 只取决包裹箭头函数的 第一个普通函数 的this。—— 箭头函数里的this由它被声明时所在的作用域控制(即指向父作用域)【牢记】 - 由于this已被提前直接绑定了,所以,无法用
call()
、apply()
、bind()
去改变 this 的指向的。即传入的第一个参数被忽略。 - 如果使用箭头函数,以前的那种hack写法:
var that = this;
就不再需要了。
箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中。
例子1:
function a() {
return () => {
return () => {
console.log(this)
}
}
}
const obj = {
num: 2,
a: a
}
// 执行1
a()()();
// 执行2
obj.a()()();
分析:
在这个例子中,因为包裹箭头函数的第一个普通函数是 a(或者理解为:箭头函数最终是在函数a中被定义的,即父作用域是函数a),所以箭头函数中的this等于函数a中的this。而a中的this指向 由a如何被调用控制。
执行1:普通函数a是直接被执行的a()
,所以此时普通函数a中的 this 是 window,所以输出结果为window。
执行2:普通函数a是被obj.a()
的方式调用的,所以此时普通函数a中的 this 是 obj,所以输出结果为obj。
例子2:
var name = 'cyc';
var test = {name:"shitou"};
var fun = () => {this.name};
fun.apply(test);
结果:cyc
对箭头函数使用apply、call、bind都是无法绑定this的。fun里的this指向了window。
例子3:
普通函数fn被直接调用(this直接指向window):
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
obj.getAge(); // NaN
箭头函数fn被调用:【典例】
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 30 写这个代码时 new Date().getFullYear()返回的是2020哈
箭头函数fn在getAge函数中被定义,所以this指向getAge中的作用域。
箭头函数不适用的场景【一定要了解】
以下6种情况是不能使用箭头函数的:
1. 不适合定义对象字面量方法
箭头函数不适合定义对象的方法(对象字面量方法、对象原型方法、构造器方法),因为箭头函数没有自己的 this,其内部的 this 指向的是外层作用域的 this
const obj = {
num: 2,
a: () => console.log(this.bar)
}
// 另一种写法
var a =()=> console.log(this.num);
const obj = {
num: 2,
a: a
}
obj.a()
undefined
箭头函数a是在全局作用域下被定义的,所以a中的this指向window。
this 并不是指向 json 这个对象,而是再往上到达全局作用域。块级作用域只有函数、let、const等才能创建哦。
2. 不适合构造函数
作为构造函数,一个方法需要绑定到对象。所以构造函数不能使用箭头函数的写法:
var Person =(name)=>{
this.name = name;
};
var p = new Person(); // 报错 Person is not a constructor
// 不可以当作构造函数,也就是说,不可以使用 new 命令
当我们用new生成一个Person的实例的时候其实要进行四个步骤:
- 生成一个新的对象
- 把构造函数里this的值指向新生成的对象
- 把这个对象绑定到它的原型对象
- 返回这个新生成的对象
但是我们通过这个箭头函数并没有实现把这个this值绑定到我们新生成的p对象上去,所以并不会得到我们想要的效果,那么这个时候我们只能使用我们的普通函数。
3. 不适合对象原型prototype
prototype中的函数不能使用箭头函数的写法:
function Person(name){
this.name = name;
}
Person.prototype.getName = ()=> console.log(this.name);;
const p = new Person();
p.getName();// undefined
this 并不是指向 p,根据变量查找规则,回溯到了全局作用域
4. 不适合真的需要this
时
箭头函数不适合定义结合动态上下文的回调函数(事件绑定函数),因为箭头函数在声明的时候会绑定静态上下文
const button = document.querySelector('button');
button.addEventListener('click', () => {
this.textContent = 'Loading...';
});
// this 并不是指向预期的 button 元素,而是 window
5. 不适合需要arguments
类数组对象时
在箭头函数当中是没有arguments这个对象的:
var Person =name=>console.log(arguments)
Person('caicai');// 报错:arguments is not defined
6. 多层函数嵌套时不建议使用箭头函数
箭头函数的亮点是简洁,有多层函数嵌套的情况下,箭头函数反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数。
3. bind
最后种情况也就是 bind 这些改变上下文的 API 了,对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window。
let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => ?
不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window
可能会发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定 this 最终指向哪里。
4. 立即执行函数表达式
var myobject = {
foo: "bar",
func: function () {
var self = this;
console.log(this.foo);
console.log(self.foo);
var test = (function () { // 普通函数的写法。若这里是箭头函数,则this值和self值相同
console.log(this.foo);
console.log(self.foo);
})();
},
};
myobject.func();
答案:bar bar undefined bar
“匿名函数的执行环境具有全局性”,所以最里层那个test函数中this指向全局环境,全局环境没有定义foo变量所以输出undefined。在匿名函数外部将this保存到一个内部函数可以访问的变量self中,可以通过self访问这个对象,所以self.foo为bar.
5. 计时器
setTimeout的回调函数为普通函数:
function foo(){
console.log(this.a)
setTimeout(function(){
console.log(this.a,this)
},0)
}
var oo = {a:1,foo:foo};
oo.foo();
答案: 1;undefined window
计时器回调函数若为普通函数,则函数中的this指向window
setTimeout的回调函数为箭头函数:
function foo(){
console.log(this.a)
setTimeout(()=>{
console.log(this.a,this)
},0)
}
var oo = {a:1,foo:foo};
oo.foo();
答案: 1;1 oo
计时器回调函数若为箭头函数,则函数中的this指向foo函数中的this,foo函数时被oo.foo()方式调用的,所以this指向oo。
聚美面试题【典例】
var name = "caicai";
var sheep = {
name: "yangyang"
};
sheep.getName = function() {
console.log(this.name);
};
sheep.getName(); //(1)
window.setTimeout(sheep.getName, 1000);(2) 聚美的原题
window.setTimeout(function(){
sheep.getName(); // (3)
}, 1000);
window.setTimeout(()=>{
sheep.getName(); // (4)
}, 1000);
答案
- 'yangyang','caicai','yangyang','yangyang'
注意:
(1)行是函数被调用(对象.的方式);
(2)行因为setTimeout的回调函数中this = window
。 因为只是传递了sheep.getName这个函数给setTimeout作为回调,而不是执行!执行时在setTimeout函数内部被执行的!
其他练习
练习1-1
var myObject = {
num: 2,
add: function () {
this.num = 3;
(function () {
console.log(this.num);
this.num = 4;
})();
console.log(this.num);
}
}
myObject.add();
答案:
undefined
3
解析:立即执行函数表达式中(这里也是闭包)中的this实际指向的是window(函数没有指明调用者呢,那就让this指向全局对象吧),但window未查找到有num属性,所以是undefined。(如果有提前声明window.num=10,那么这里就会输出10而不是undefined啦)
练习2-1
var obj1 = {
obj2: {
obj3: function () {
console.log(this);
}
}
};
console.log(obj1.obj2.obj3());
答案:{obj3: ƒ},其实obj1.obj2.obj3()
这时候的this 就等于 obj1.obj2。
练习2-2
var obj={
a:2,
b:{
a:3,
c:function(){
this.a = 4;
return this.a;
}
}
}
console.log(obj.a);
console.log(obj.b.a);
console.log(obj.b.c()); // 这时c函数里的this即指代obj.b
console.log(obj.b.a);
答案:
2 3 4 4