前言
在js中this是比较重要的概念,this在不同地方表示意思可能不同,一个简单的操作就可能会改变this的意思,所以怎么识别当前this是非常重要的
执行上下文
this是执行上下文的一个特殊的属性,所以要了解this就必须了解执行上下文,执行上下文简而言之就是当前代码的执行环境,首次运行js代码时会创建一个全局执行上下文并压入执行栈(后进先出)中,当遇到调用函数时会为该函数创建一个新的执行上下文,函数执行完毕对应的执行上下文后从执行栈中移除,即:该执行环境销毁,环境内保存的变量和函数销毁
至此我们已经知道执行上下文分为两种(eval这里不做讨论):
-
全局执行上下文:这里的
this浏览器中通常指window -
函数执行上下文:函数被调用的时候才会创建
执行上下文而非定义时,this作为执行上下文的一个属性也只能在函数被调用的时候才能确定,所以通常会说:谁调用this就是谁
接下来我们看几个demo:
// 此demo了解执行栈后进先出
function test() {
console.log('I am test')
test1()
console.log('I am test2')
}
function test1() {
console.log('I am test1')
}
test()
// 执行过程:
// 1. 创建全局执行上下文推入执行栈
// 2. 遇到test创建test执行上下文推入执行栈
// 3. 遇到test1创建test1执行上下文推入执行栈
// 4. test1执行完毕,test1执行上下文出栈,销毁
// 5. test执行完毕,test执行上下文出栈,销毁
// 6. 关闭浏览器,全局执行上下文出栈,销毁
var a = 1
var obj = {
a: 2,
fn: function () { // 函数被创建,this不明确,具体看被谁调用
console.log('a--', this, this.a)
return function() {
console.log('c--', this, this.a)
}
}
}
// 调用一
obj.fn() // 此时函数被obj调用
// 执行结果: a-- {a: 2, fn: ƒ} 2
// 调用二
var fn = obj.fn
fn()// 相当于window.fn(),此时this指向window
// 执行结果:a-- Window {window: Window, self: Window, document: document, name: "", location: Location, …} 1
// 调用三
obj.fn()()
// 执行结果:
// a-- {a: 2, fn: ƒ} 2
// c-- Window {window: Window, self: Window, document: document, name: "", location: Location, …} 1
// fn()被obj调用所以this指向obj,a是2,第二个函数实则是被window调用,所以this是window,a是1
// 函数所谓的被谁调用就是看who.function中的who,谁是who,this就是谁,没有who.,this就是window
总结:this是执行上下文属性,新的执行上下文才有可能存在新的this指向,可以创建执行上下文的有全局和函数,除函数内this,其余this均为window,函数this被调用时确认:who?.function,who是谁函数中this就是谁,没有who时函数this为window
js中会有一些特殊的存在,接下来我们一起了解一下
New
new做了以下事情:
- 创建新对象
- 新对象__proto__指向函数的prototype
- this指向该对象
- 返回新对象
new的实现:
function Animal(type) {
this.type = type
return
}
Animal.prototype.say = function() {
console.log('say')
}
// 自定义new
function myNew(...values) {
let [ fn, ...others ] = values
let obj = {}
obj.__proto__ = fn.prototype
const result = fn.apply(obj, others)
// fn返回的若为对象则返回改对象,否则返回obj
return Object.prototype.toString.call(result) == '[object Object]' ? result : obj
}
const animal = myNew(Animal, 'v')
console.log('animal', animal)
// animal Animal {type: "v"}
从new的实现可以看出改变Animal中this的指向了新对象obj
Call、Apply和Bind
call、apply和bind都可以改变this的指向,它们的实现如下:
// call
Function.prototype.myCall = function(...values) {
let [ obj, ...others ] = values
obj = obj ? Object(obj) : window
obj.fn = this
obj.fn(...others)
delete obj.fn
return
}
// apply
Function.prototype.myApply = function(...values) {
let [ obj, others ] = values
obj = obj ? Object(obj) : window
obj.fn = this
obj.fn(others)
delete obj.fn
return
}
// bind
Function.prototype.myBind = function(...values) {
let [ obj, others ] = values
let _self = this
let bind = function(vl) {
return _self.apply(obj, [...others, ...vl])
}
bind.prototype = Object.create(this.prototype)
return bind
}
ok以上是它们的实现,可以看出它们三个都有改变this的作用,还是以demo为例:
var a = 1
var obj = {
a: 2,
fn: function () {
console.log('a--', this, this.a)
return function() {
console.log('c--', this, this.a)
}
}
}
// 调用一
var fn = obj.fn
fn.call(obj)
// 执行结果: a-- {a: 2, fn: ƒ} 2,不同于之前的结果这里的this指向了obj
// 调用二
obj.fn().call(obj)
// 执行结果
// 1. a-- {a: 2, fn: ƒ} 2
// 2. c-- {a: 2, fn: ƒ} 2
// 由于我们使用call改变第二个函数的this指向,所以此时的this为obj,再次证明函数被调用的时候才能确定this指向
箭头函数
箭头函数是一个特殊的存在,没有自己this,没有prototype,没有arguments,不能作为构造函数,它继承的是上层作用域链的this,箭头函数this的指向创建便确定,一旦确定即使call、apply和bind也无法改变,一起看几个demo吧
var obj = {
fn: () => {
console.log('this', this)
}
}
// 调用一
obj.fn()
// 执行结果: this Window {window: Window, self: Window, document: document, name: "", location: Location, …}
// 调用二
obj.fn.call(obj)
// 执行结果: this Window {window: Window, self: Window, document: document, name: "", location: Location, …}
// 执行结果都是window,因为箭头函数被创建时this的指向已经确定为window
再看一个
var obj = {
fn: function() {
return () => {
console.log('this---', this)
}
}
}
// 调用一
obj.fn()()
// 执行结果:this--- {fn: ƒ}
// 调用二
const fn = obj.fn()
fn()
// 执行结果:this--- {fn: ƒ}
// 调用三
obj.fn.call(window)()
// 执行结果: this Window {window: Window, self: Window, document: document, name: "", location: Location, …}
// 第三次结果不同,是因为通过call将fn的this指向了window,也就是说箭头函数依赖的this指向了window,所以箭头函数的this指向window
结语
执行上下文还有一些概念,如:变量提升、函数提升、作用域链等,不做过多的解释了,不了解了小伙伴自行查阅一下