学习this指向之前需要先去了解执行上下文、执行栈、作用域、作用域链相关的知识
什么是this?
首先,js执行分为创建阶段、执行阶段,创建阶段包含词法分析、语法分析、作用域规则确定,执行阶段包含创建执行上下文、执行函数确定this、垃圾回收。其次js引擎通过执行栈管理执行上下文,全局执行上下文、函数执行上下文 所以作用域沿着创建该函数的地方向上找、this执行的时候谁最后调用就指向谁。this就是指当前执行函数的上下文。函数体里面的this.x就是指当前运行环境的x。在哪个上下文执行,this就指向该上下文。
如何判断this指向?
优先级:new>call apply bind>方法调用>函数调用
- 全局环境:this指向window
- 构造函数:this指向新实例对象
- 对象的方法中:this指向该方法运行时所在的对象
- call调用对象必须是函数,首先判断是否为函数
- 匿名函数的this是指向全局对象的,所以this指向window
- new时候this指向window或者构造函数
this在哪个方法中,它就指向这个方法所在的对象。由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即this的指向是可变的。
如何改变this指向?
new
- 创建一个空对象,作为将要返回的对象实例
- 将空对象的原型指向构造函数的prototype属性
- 将构造函数的this指向这个空对象,并执行构造函数同时带上new时传入的参数。apply
- 返回的是对象就直接返回,否则返回这个新对象
function news(constructor, params) {
let args = Array.from(arguments)
let cons = args.shift()
let obj = Object.create(cons.prototype)
let res = cons.apply(obj, args)
return typeof res ==='object'&&res!==null?res:obj
}
call
- 判断调用call的是否为函数,判断传入的新this,若没有设为window
- 新this上添加symbol属性key,将旧this作为方法放入新this中
- 执行该方法携带参数拿到返回值
- 删除symbol属性
- return 执行的结果
function call(context) {
if (typeof this !== 'function') {
console.error();
}
context = context || window
let key = Symbol()
context[key] = this
let args = [...arguments].slice(1)
let res = context[key](...args)
delete context[key]
return res
}
apply
和call一样,就是执行的时候,参数取...arguments[1]是个集合
bind
参数和call一样从第一个截取,保留新this 旧this和参数,return函数,判断新this instanceof Fn?this:旧this,拼接参数。
function bind(context) {
if (typeof this !== 'function') {
}
context = context || window
let fn = this
let args = [...arguments].slice(1)
return function Fn() {
fn.apply(this instanceof Fn?this:context,args.concat(...arguments))
}
}
call apply bind有什么区别?
- 参数不同:call apply bind第一个参数都是this,call bind后面参数要列举出来,apply第二个参数是剩余参数的集合[]
- 函数执行时机不同:call apply会直接执行函数,bind会保留旧this,arguments,return函数进行参数拼接