前言:大家好,我是林一一,这是一篇关于 this 指向的文章。this 是 JS 中一个特别重要的一个知识点,this 难吗?好像挺简单的。看完下面这一篇文章,还不会的话,你别来找我。手动狗头🐕。
思维导图
this 的指向
- 执行函数前有
'.'点操作符的话,函数体中的this就指向前面的对象,没有就指向window,严格模式下指向undefined。这句话特别的重要,请记住 - 函数没有直接调用者
this指向全局对象(浏览器中是window,node中是 global)。如匿名函数等 - 构造函数的
this指向实例本身。 - 箭头函数本身没有
this的,箭头函数的this指向最近的非箭头函数this,找不到就指向window,严格模式下指向undefined。
再来看一下这句话:执行函数前有
'.'点操作符的话,函数体中的this就指向前面的对象,没有就指向window
一、普通函数 this 的热身题
热身题 1
var name = '林一一'
function fn(){
var name = '林二二'
return this.name
}
fn() // 林一一
执行函数
fn(),前面没有'.'点操作符吧,那么这里的this就指向window。输出的就是全局下的name = '林一一'。
再来看一下这句话:执行函数前有 '.' 点操作符的话,函数体中的 this 就指向前面的对象,没有就指向 window
热身题 2
var name = '林二二'
var obj = {
name: '林一一',
fn: function () {
return this.name
}
}
console.log(obj.fn()) // '林一一'
var fo = obj.fn
console.log(fo()) // '林二二' fo() ==> window.fo()
obj.fn()中函数fn()前面有'.'点操作符吧,那么这里的this就指向obj这个对象。再看执行函数fo(),前面没有'.'点操作符吧,那么这里的this就指向window。其实上面的函数fo() ==> window.fo(),所以执行函数fo()前面也是可以看作是有'.'操作符的。
再来看一下这句话:执行函数前有 '.' 点操作符的话,函数体中的 this 就指向前面的对象,没有就指向 window
热身题 3,修改一下热身题 2
var name = '林二二'
var obj = {
name: '林一一',
fn: function () {
var name = '小三'
return function(){
return this.name
}
}
}
console.log(obj.fn()()) // 林二二
var fo = obj.fn()
console.log(fo()) // 林二二
热身3和热身2差不多,
obj.fn()()中obj.fn()执行完后有一个函数(这里称为函数A)返回,最后相当于执行函数A(),A()前面没有'.'点操作符吧,那么这里的this就指向window,输出就是林二二了。上面的fo()函数同理。
二、函数没有直接调用者(更新)
函数没有直接调用者 this 指向全局对象(浏览器中是window,node中是 global),如匿名函数等;严格模式下指向 undefined
热身题 1
var name = '林一一';
!(function(){
console.log(this.name) // 林一一
})()
自执行函数没有直接的调用者输出的
name = '林一一'。
热身题 2
var name = '林一一'
var obj = {
name : '二二',
callback: function(){
console.log(this.name)
}
}
setTimeout(obj.callback,1000)
/* 输出
* 林一一
*/
函数
setTimeout,obj.callback(这只是一个引用地址)中并没有直接调用者,this就指向window。所以输出的name就是全局下的林一一。
三、构造函数中的 this
来读一下这句话:构造函数的 this 指向实例本身
关于构造函数的
this为什么指向实例是浏览器指定的,详情看new这个过程发生了什么 面试 | 你不得不懂得 JS 原型和原型链
热身题 1
function Fn(){
var n = 0
this.name = '林一一'
this.age = 18
this.getName = function(){
return this.name
}
}
Fn.prototype.getAge = function(){
return this.age
}
Fn.x = '林二二'
var f = new Fn()
console.log(f.name) // 林一一
console.log(f.getName()) // 林一一
console.log(f.getAge()) // 18
console.log(f.n) // undefined
console.log(f.x) // undefined
上面的
Fn经过new后就是一个构造函数,this就指向实例f。所以上面的1,2输出都是林一一。f.getAge()是实例f调用了getAge输出就是 18,问:实例f中并没有属性getAge是怎么输出 18的,f.x输出又为什么是undefined?答:这是原型链的查找机制,属性x不是在原型prototype上的就不是实例的属性,可以读一下这篇文章 面试 | 你不得不懂得 JS 原型和原型链;问:为什么f.n输出的是undefined。因为变量n是构造函数的私有变量和new创建的实例没有关系。
四、箭头函数
- 箭头函数本身没有
this,箭头函数的this继承上下文的,里面的this会指向当前最近的非箭头函数的this,找不到就是window(严格模式是undefined) - 箭头函数的this,是定义时指定的不是执行时指定的
热身题 1
var name = '林一一'
var obj = {
name: '二二',
a: () => {
console.log(this.name)
}
}
obj.a()
/* 输出
* '林一一'
*/
箭头函数的
this,找不到非箭头函数的this就直接指向window。
热身题 2
var name = '林一一'
var obj = {
name: '二二',
fn: function() {
return () => {
console.log(this.name)
}
}
}
obj.fn()()
/* 输出
* '二二'
*/
很明显箭头函数的
this来自函数fn,对象obj调用了函数fn,所以fn的this指向obj,输出结果就是二二。 一定要注意的是,如果这里一定要要求大英 obj 的 name 属性,就只能把箭头函数修改成 function 的形式。。。。
五、call,apply,bind 改变 this 的指向
提示:所有的函数都是基于 Function 这个基类来创建的,同样拥有 Function 原型上面的方法
call,接受this的对象,和一组列表。apply和call一样,唯一不同的是apply接受的是一个包含多个参数的数组。bind同样也是改变函数的this指向,只不过bind执行后会返回一个新的函数,新函数中参数来源于剩余的参数
热身题
var name = '林一一'
var age = 18
function fn(){
return this.name
}
function p(){
return {
age: this.age,
arg: arguments
}
}
let obj = {
name: '二二',
age: 18
}
let o = {
name: '三三'
}
fn() // '林一一'
fn.call(obj) // '二二'
fn.call(o) // '三三'
p.call(obj, 12, 23, 45, 67) // {age: 18, arg: Arguments(4)}
fn.apply(obj) // "二二"
p.apply(obj, [1, 2, 3, 4, 5]) // {age: 18, arg: Arguments(5)}
fn.bind(obj)() // "二二"
p.bind(obj, 12, 23, 34)() // {age: 18, arg: Arguments(3)}
以上就是
call,apply,bind, 关于this的内容,这里不介绍三者的写法,如果介绍可以写另一篇文章了。对这三者不熟悉的可以找其他资料看看。
六、new 改变 this 的指向
- new 也可以改变 this 的指向,将构造函数的 this 指向 实例堆内存地址,而实例的堆内存指向构造函数的原型。
思考题(字节)
var val = 10
let a = function () {
console.log(this.val)
}
a.prototype.val = 9
val = 3
a(); // 3
let b = new a(); // 9
console.log(b.val) // 9
由于
new改变了this的指向,所以new a();时 a 的 this 指向实例的堆内存地址,而实例的堆内存的原型链指向 a 的原型,相当于访问的就是堆内存地址,所以结果输出9。
思考题
1. 笔试题 this 指向问题
var name = '林一一'
var obj = {
name: '林二二',
show: function (){
console.log(this.name)
},
wait: function () {
var fn = this.show
fn()
}
}
obj.wait() // 林一一
obj.wait()中,执行函数wait()前面有'.'点操作符吧,那么这里的this就指向obj这个对象,所以this.show ==> obj.show。再看执行函数fn()前面没有'.'点操作符吧,那么这里的this就指向window,输出就是林一一。
2. 阿里 this 指向和原型面试题(新增)
new 创建函数的过程中发生了什么可以看这篇 面试 | 你不得不懂得 JS 原型和原型链
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();
let obj = new Foo();
obj.a();
Foo.a();
/*
* 4
* 2
* 1
*/
Foo.a();中直接调用函数的私有方法a输出结果就是 4。new Foo();过程中函数的私有a被重新赋值,同时原型prototype上的属性a也被重新赋值。所以obj.a();输出结果就是 2,Foo.a();输出结果就是 1。
3. 和闭包有关的 this 指向问题
var n = 2 // -> 4 -> 8
var obj = {
n: 3, // 6
fn: (function(n){
n*=2
this.n+=2 // window 下的 n 变成 4
var n = 5 // 这一步不会再重新声明,因为已经参数赋值,就不会再声明而是直接赋值 n = 5
console.log("window.n", window.n)
return function (m) {
console.log("n:", n, "m", m) // n:5 m:3 这里的 n 向上查找是 5 //
this.n*=2 // fn(3): 2 * 4 =8 // obj.fn(3): 2 * 3 = 6
console.log(m + (++n)) // 3 + (++5) ++n 导致上级作用域的n变成了6 // 3 + (++6)
}
})(n)
}
var fn = obj.fn;
fn(3) // 9
obj.fn(3) // 10
console.log(n, obj.n) // 8 6
/* 输出
* 9
* 10
* 8 6
/
这道题就留给大家思考了,上面有我的分析步骤,觉得碍眼的话可以去掉 😂。你可以在评论区给出你的分析过程。
结束
感谢阅读早这里,我是林一一,如果这文章能对你有一点启发的话,欢迎给个 star, 下次见。