一网打尽this,对执行上下文说Yes
this 到底指向谁?
- 说到这里,还得回归JS的基本概念-执行上下文 这个概念之后会进行扩展
这里可以先死记硬背几条规律
- 在函数体中,非显式或者隐式简单调用函数时,
- 严格模式下,会被绑定到
undefined
上面 - 非严格模式,会被绑定到全局对象
window
上
- 严格模式下,会被绑定到
- 一般使用
new
方法调用构造函数,内部的this会被绑定到新创建的对象
- 一般使用
call/apply/bind
方法调用函数,会指向传进去的参数对象
- 一般使用上下文对象调用函数,this会绑定到
上下文对象
- 在箭头函数中,this指向是由外层(函数或者全局作用域)决定的
1 全局中的this
function f1():void{
console.log(this)
}
function f2():void{
'use strict'
console.log(this)
}
f1() // window
f2() // undefined
这道题相对简单,严格模式下,会被绑定到undefined
上面,非严格模式,会被绑定到全局对象window
上
1.1 但是需要注意变式
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
var fn1 = foo.fn
fn1()
这里的this依然指向`window`,虽然fn函数是作为foo方法调用的,但是在赋值给fn1后,fn1的调用依然是在全局对象中进行的,因此会输出`window`和`undefined`
- 等价于以下代码
console.log(window)
console.log(window.bar) // 去window 上找bar,那可不就undefined了嘛
1.2 变式2
如果改成以下形式
const foo = {
bar: 10
fn: function(){
console.log(this)
console.log(this.bar)
}
}
foo.fn()
- 输出将会是
{bar: 10,fn: f}
10
这时,this指向的是最后调用他的对象,
法则1:
在执行时候不考虑显式绑定,如果函数中的this是被上一级调用的就是此对象,否则就是全局环境
2 上下文中的this
2.1 普通上下文对象
const student = {
name: 'lucas',
fn: function(){
return this.name
}
}
console.log(student.fn() === 'lucas') // true
2.2 this指向最后调用的对象
const person = {
name: 'lucas',
brother: {
name: 'Mike',
fn: function() {
return this.name
}
}
}
console.log(person.brother.fn()) // Mike
2.3 是不是很简单?(进阶)
const o1 = {
text: 'o1';
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2';
fn: function() {
return o1.fn()
}
}
const o3 = {
text: 'o3';
fn: function() {
var fn = o1.fn
return fn()
}
}
- 第一个很简单是指向上下文,输出
o1
- 第二个也很简单去调用这个
o1.fn()
,还是o1
- 第三个也很简单,由于变量提升,以上代码会转化成如下代码
const o3 = {
text: 'o3';
fn: function() {
var fn; // undefined
fn = o1.fn;
return fn()
}
}
console.log(o3.fn())
- 相当于
var fn = o1.fn;
从右向左o1.fn
的fn方法赋值给fn
. - 此时,
fn = o1.fn
等价fn = fn()
,右边的fn
是o1
的方法 - 裸调用一个方法,
fn()
等价于window.fn()
- 此时,相当于
o3.window.fn()
当然是undefined
了
答案是 o1 o1 undefined
2.4 简单吗?再进阶一下
如果此时这面试题你全部写对,面试官会问,那怎么让第二个输出02
呢?
- 你会想到那还不简单?
- bind,call,apply
- 但是interviewer会继续问,除了这些呢?
这个问题就考察思考问题的深度和随机应变了
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: o1.fn
}
console.log(o2.fn())
- 刚才已经说到,this指向最后调用他的对象,在上面代码我提前将
o1
的fn
方法通过赋值操作,挂载到o2
的fn
属性上
// 相当于这样
const o2 = {
text: 'o2',
fn: function() {
return this.text
} // 这个function 是原本o1的方法
}
3 bind,call,apply
3.1 区别
这个问题比较基础
- bind和apply,call的联系是都可以显式重定 this 指向到传递进去的参数对象
- 区别是:
- bind 会创建一个新的函数,不执行之前的函数
- apply call 第一个参数都是需要
重定向的对象
,前者需要其他参数传递一个数组,call则以字符串传递
一句话总结就是 apply 和 call,bind都会改变相关函数的
this
指向,call apply 会执行原函数,bind不会执行原函数,而是返回新的函数,这个新的函数已经自定绑定了新的this指向,并且可以手动调用它
再具体一点就是call apply区别在参数设定,上面已经说过了
- 用代码来总结的话,以下的3个代码都是等价的
// 1
const target = {}
fn.call(target,'arg1','arg2')
// 2
const target = {}
fn.apply(target,['arg1','arg2'])
// 3
const target = {}
fn.bind(target,'arg1','arg2')
const fo = {
name: 'lucas',
logName: function():void {
console.log(this.name)
}
}
const bar = {
name: 'mike'
}
console.log(fo.logName.call(bar)) // 由 lucas => mike
本身不难理解,但是一般会结合继承来实现,之后单独写文讲
4 构造函数中的this
4.1 这个直接看题就行
function Fo() {
this.bar = 'lucas';
}
const instance = new Fo();
console.log(instance.bar) // 'lucas'
可能 interviewer 会接着问 new 操作符,提供简略班回答
- 创建了新对象
- 构造函数的this指向新对象
- 可以添加属性方法
- 并返回这个新的对象
const obj = {}
obj.__proto__ = Foo.prototype;
Foo.call(obj)
新对象 => 构造函数的原型对象给 新对象的原型 => 构造函数通过call绑定到新对象
当然这里是简单版本,后续会手动实现new bind apply call 等,后续再说
4.2 如果构造函数中有return的情况
function Foo() {
this.user = 'lucas';
const p = {};
return p
}
const i = new Foo()
i.user// this => {}
function Foo() {
this.user = 'lucas';
const p = {};
return 1
}
const i = new Foo()
i.user// this => 1
- 相信大家猜到了,
return 复杂类型就是这个对象,如果是简单类型就是new操作符号的实例
5 箭头函数
先说结论,(哎呦不错哦),不好意思说错了
- 外层(函数或者全局作用域决定) 书中是this: the enclosing(function or global) scope
setTimeout
const foo = {
fn() {
setTimeout(function(){
clg(this)
},1000)
}
}
clg(foo,fn()) // 匿名函数的this => window
const foo = {
fn () {
setTimeout(()=>{
clg(this)
},1000)
}
}
clg(foo,fn()) // {fn,f}
5.2 this的优先级
箭头函数的没有难度,一般会配合this优先级
- call apply bind new => 显式
- 根据调用关系 => 隐式
function foo (a) {
clg(this.a)
}
const obj1 = {
a:1,
foo:foo
}
const obj2 = {
a:2,
foo:foo
}
obj1.foo.call(obj2) // 2
obj2.foo.call(obj1) // 1
- 显式 > 隐式
bind 和 new
function foo(a) {
this.a = a
}
const obj1 = {};
var bar = foo.bind(obj1);
bar(2)
clg(obj1.a) // {a: 2}
bind 将 bar函数中的this绑定到obj1上,bar(2)执行后,obj.a = 2
- 再使用构造函数
const bar = new Bar(3)
clg(bar.a) // 3
bar本身是通过bind 构造函数,内部已经将this绑定为obj1,再次作为构造函数new调用时候,返回的实例就和obj1解绑了,也就说,new 绑定了修改bind
new > bind
function foo () {
return a => {
clg(this.a)
}
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
const bar = foo.call(obj1)
clg(bar.call(obj2)) // 2
// 由于foo 中的this 绑定到了obj1上,所以 bar 中的this 会绑定到obj1,箭头函数的绑定无法修改
说到这里,开放的立例题分析:
实现一个面试经典的bind函数,有兴趣的可以做一下,下次更新闭包和补充this吧,还有小部分可以写,bye