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. spring boot异常积累

    1.异常:Error resolving template "xxx", template might not exist or might not be accessible.. ...

  2. python3 Django集成Ueditor富文本编辑器

    1.下载 python3: https://github.com/twz915/DjangoUeditor3/ (直接下载zip) 2.解压,解压打开后找到DjangoUeditor将DjangoUe ...

  3. 数据可视化——阿里云解决方案DataV

    数据可视化——阿里云解决方案DataV https://help.aliyun.com/document_detail/53844.html?spm=a2c4g.11186623.6.579.37fd ...

  4. js除法四舍五入

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <hea ...

  5. Jmeter简单的接口测试举例

    推荐文章:http://www.cnblogs.com/puresoul/p/5092628.html 1.创建线程组 本次测试模块为一个线程组(可以在线程组内列出模块内的需要测试的接口) 2.在线程 ...

  6. 跨主机网络overlay和macvlan模型

    overlay网络模型 无论是openstack还是docker都是先创建一个网络然后再创建虚机或者容器  并把创建的虚机或者容器运行在此网络中 Docker 提供了 overlay driver,使 ...

  7. python tkinter entry

    """小白随笔,大佬勿喷""" '''Entry编辑框 收集数据''' import tkinter as tk import tkinte ...

  8. selenium的

    1.简介 selenium可以认为是反反爬虫的最佳利器,它基本可以等同于真实的浏览器访问,用它可以加载到动态数据,也省去了cookie的操作,但是用这个有一个重大的效率问题.所以selenium可以用 ...

  9. 3.3.1 MyBatis框架介绍

    MyBatis框架介绍 1. 什么是框架 (1) 框架是偷懒的程序员将代码进行封装, 之后进行重复使用的过程. (2) 框架其实是一个半成品, 以连接数据库为例, 连接数据库使用的驱动, url, 用 ...

  10. iOS聊天客服功能(Udesk)

    前言: 在商城或者需要用户和平台交流的APP中需要用到客服功能.接下来会讲解Udesk客服功能的实现.技术文档在Udesk官网可以看到,本文讲述在项目中的集成实现流程.客服使用的PC端交流工具账号在公 ...