有关this

时间:2023-03-08 22:30:49

thisJavascript函数内部的一个特殊对象,引用的是函数运行时的环境对象,也就是说,this是动态的(箭头函数除外),是在运行时进行绑定的,并不是在编写时绑定(箭头函数是编写时绑定)。 this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

绑定规则

this绑定根据函数的调用方式基本上有四种规则:

全局性调用

函数的最通常用法,this代表全局对象:

function sayColor() {
console.log(this.color)
}
var color = 'red'
sayColor() // red

作为对象的方法调用

函数作为某个对象的方法调用时,this指向这个上级对象:

let car = {
color: 'black',
sayColor
}
car.sayColor() // black

当存在多重调用时,最后一层会决定调用位置:

let house = {
color: 'white',
car
}
house.car.sayColor() // black

使用apply、call、bind方法绑定调用对象

函数可以使用apply、call、bind方法来改变调用对象。这些方法的第一个参数是一个对象,它们会把这个对象绑定到this

let bindObj = {
color: 'green'
}
sayColor.apply(bindObj) // green
sayColor.call(bindObj) // green
sayColor.bind(bindObj)() // green

第一个参数为空时,默认绑定全局对象:

sayColor.apply()  // red

构造函数调用

当函数作为构造函数被调用时,this指向创建的新对象:

function Person(color) {
this.color = color
}
let p = new Person('yellow')
p.color // yellow

使用new操作符调用构造函数时,首先会创建一个新对象,然后将构造函数的作用域赋给新对象,this就指向了新对象,过程与下面代码类似:

let o = new Object()
Person.call(o, 'yellow')
o.color // yellow

当然,构造函数不通过new调用时,与普通函数无区别,可以理解为实际上并不存在所谓的构造函数,只有对于函数的构造调用

Person('yellow')
// this指向了全局对象window
window.color // yellow

以上就是this绑定的四种规则,函数调用时,先找到调用位置,然后判断需要应用四条规则中的哪一条。

应用

现在来看下实际应用的几种特殊情况。

回调函数

当做回调函数调用时,一般是全局调用:

setTimeout(sayColor) // red 指向window

let obj = {
color: 'green',
say () {
sayColor()
}
}
obj.say() // red 指向window

但在一些上下文中会进行隐式绑定,比如事件中的this是指向于事件的目标元素的,还有一些数组的操作方法可以使用第二个参数来绑定this

[1, 2, 3].forEach(function () { console.log(this.color) })  // red red red  指向window
[1, 2, 3].forEach(function () { console.log(this.color) }, car) // black black black 指向car对象

绑定丢失

function sayColor() {
console.log(this.color)
}
let car = {
color: 'black',
sayColor
}
var color = 'red' let alias = car.sayColor
alias() // red

上述代码中将对象的方法赋值给新变量aliasalias函数执行时,this指向了全局对象。

函数的名字仅仅是一个包含指向函数对象的指针的变量,car对象的sayColor属性保存在一个属性描述符对象中:

Object.getOwnPropertyDescriptor(car, 'sayColor')
// {value: ƒ, writable: true, enumerable: true, configurable: true}

其中描述符对象的value属性保存了指向sayColor函数的指针,let alias = car.sayColor语句将指向sayColor函数的指针赋值给变量alias,执行alias函数就是在全局对象中执行函数sayColor

当做回调函数时:

setTimeout(car.sayColor, 500)  // red  指向window

因为Javascript中的函数参数都是按值传递的,上述代码将指向sayColor函数的指针赋值给了setTimeout函数的参数,也相当于在全局环境中执行sayColor函数。

闭包

匿名函数的执行一般具有全局性,在闭包中由于编写方式可能不会那么明显:

let obj = {
color: 'green',
say () {
return function() {
console.log(this.color)
}
}
}
obj.say()() // red this指向全局window

内部匿名函数是有自己的this变量的,所以无法访问到外部函数saythis变量,我们可以将外部this变量保存于一个闭包能够访问的变量之中:

let obj = {
color: 'green',
say () {
let self = this
return function() {
console.log(self.color)
}
}
}
obj.say()() // green

当然,现在可以用箭头函数来绑定this:

let obj = {
color: 'green',
say () {
return () => {
console.log(this.color)
}
}
}
obj.say()() // green

优先级

全局性调用优先级是最低的。

使用apply等函数绑定this的优先级高于对象调用:

let car = {
color: 'black',
sayColor
}
let bindObj = {
color: 'green'
} car.sayColor() // black
car.sayColor.apply(bindObj) // green

使用new操作符绑定高于使用apply等函数:

function Person(color) {
this.color = color
}
let obj = {}
let bindPerson = Person.bind(obj)
let p = new bindPerson('yellow') p.color // yellow this指向了创建的对象p
obj.color // undefined 没有指向obj

箭头函数与this

ES6中引进了箭头函数,可以简化匿名函数的语法:

setTimeout(() => { console.log(this.color) }, 50)

箭头函数内部是没有thisargumentssupernew.target特殊变量的,访问它们时会指向最近的外层非箭头函数的相应变量:

function sayColor() {
return () => {
return () => {
console.log(this.color)
}
}
} sayColor.call({ color: 'red' })()() // red 指向了外层sayColor函数的this对象
sayColor.call({ color: 'red' }).call({ color: 'green' })() // red 依然指向外层sayColor函数的this对象
sayColor.call({ color: 'red' }).call({ color: 'green' }).call({ color: 'yellow' }) // red箭头函数使用call是无法绑定this的

所以,箭头函数可以起到固定化this指向的效果,一定程度上可以说this是静态的,参考上面闭包的代码:

// ES6箭头函数
let obj = {
color: 'green',
sayColor () {
return () => {
console.log(this.color)
}
}
} // ES5
let obj = {
color: 'green',
sayColor () {
let self = this
return function() {
console.log(self.color)
}
}
}

当然,静态并不意味着箭头函数的this是永远不变的,而是随着外层函数的this变化而变化:

let obj = {
color: 'green',
sayColor () {
return () => {
console.log(this.color)
}
}
} obj.sayColor()() // green
obj.sayColor.call({ color: 'red' })() // red

不适用情况

在事件中想将this指向目标元素时,箭头函数是不适用的:

btn.addEventListener('click', () => {
console.log(this)
})

上述代码中this指向了全局对象,而不是事件的目标元素。

将函数当做对象的方法调用并且想将this指向对象时,也是不适用的:

let obj = {
color: 'green',
sayColor: () => {
console.log(this.color)
}
}

上述代码中this也指向了全局对象。

总之,需要this动态时使用非箭头函数,需要this静态时使用箭头函数:

function Person() {
this.color = 'yellow'
setTimeout(() => { console.log('person color is',this.color) }, 50)
setTimeout(function() { console.log('global color is',this.color) }, 50)
}
var color = 'red'
new Person()
// 输出
person color is yellow
global color is red

随机推荐

  1. Autofac之自动装配

    从容器中的可用服务中选择一个构造函数来创造对象,这个过程叫做自动装配.这个过程是通过反射实现的 默认 思考这么一个问题,如果注册类型中存在多个构造函数,那么Autofac会选择哪一个来创建类型的实例 ...

  2. 2018-2019-2 网络对抗技术 20165225 Exp3 免杀原理与实践

    2018-2019-2 网络对抗技术 20165225 Exp3 免杀原理与实践 一.实验说明 1.1 正确使用msf编码器(0.5分),msfvenom生成如jar之类的其他文件(0.5分),vei ...

  3. RemoveError: 'requests' is a dependency of conda and cannot be removed from conda's operating environment

    (base)$ conda update conda conda remove -n *** --all 或者 (base)$ conda update --force conda conda rem ...

  4. 从合并两个Map说开去 - foldLeft 和 foldRight 还有模式匹配

    开发中遇到需求:合并两个Map集合对象(将两个对应Key的值累加) 先说解决方案: ( map1 /: map2 ) { )) ) } 首先: Scala中现有的合并集合操作不能满足这个需求 . 注意 ...

  5. Linux学习路线全解,Linux操作系统学习路线

    大家都知道,在现在这个信息化飞速发展的时代,IT技术火速发展,信息的重要性,可想而知.现在,在北京当一个高级运维工程师,年薪百万已经不是梦想.当然我也想,谁不想挣大钱,开好车,住好房.下面说说自己的一 ...

  6. AES加密解密 助手类 CBC加密模式

    "; string result1 = AESHelper.AesEncrypt(str); string result2 = AESHelper.AesDecrypt(result1); ...

  7. [TJOI2009]猜数字

    题目描述 现有两组数字,每组k个,第一组中的数字分别为:a1,a2,...,ak表示,第二组中的数字分别用b1,b2,...,bk表示.其中第二组中的数字是两两互素的.求最小的非负整数n,满足对于任意 ...

  8. leetcode网学习笔记(1)

    https://leetcode-cn.com/problems/reverse-linked-list/submissions/ 206 反转链表 错误原因:没有考虑到链表为空以及链表只有一个元素的 ...

  9. Zepto源码分析之二(新旧版本zepto.Z方法的区别)

    在上一节中讲到Z()方法,是在初始化函数init中直接调用zepto.Z() zepto.Z = function(dom, selector) { dom = dom || [] dom.selec ...

  10. Qt如何在QTabWidget上绘图

    Qt绘图事件必须在paintEvent事件下绘图,这样导致我们在向Qt设计师界面上拖拽的控件绘图时,造成了很大的麻烦. 我们不能在拖拽的控件上写paintEvent函数,但是可以自定义一个类,继承某一 ...