# this 指向问题、call/bind/apply
# 经典 this 指向问题
# this 指向的表现形式有哪些
预编译过程中,this 指向全局对象 window;
在严格模式下"use strict",为 undefined.
对象的方法里调用,this 指向调用该方法的对象.谁调用它就指向谁
构造函数里的 this,指向创建出来的实例.
# 改变 this 指向的方法有哪些
- bind
- call
- apply
# 例题分析
function foo() {
bar.apply(null, arguments);
}
function bar() {
console.log(arguments);
}
foo(1, 2, 3, 4, 5);
查看答案
[1,2,3,4,5]
因为bar.apply(null,arguments)相当于执行`bar(arguments)`,上层foo将实参列表传递过来,然后作为bar的实参传入,所以打印数组[1,2,3,4,5]
超经典试题
var name = "222";
var a = {
name: "111",
say: function() {
console.log(this.name);
}
};
var fun = a.say;
fun();
a.say();
var b = {
name: "333",
say: function(fun) {
fun();
}
};
b.say(a.say);
b.say = a.say;
b.say();
查看答案
222,111,222,333
1. 首先分析fun被赋值了a对象里面的say方法,然后注意看fun没有谁调用,所以这个时候this走预编译,指向window,打印222
2. a.say(),谁调用指向谁,this指向a,所以打印111
3. b.say(a.say),代表b对象的say方法的this指向b,然后a对象的say方法传入b的say方法,方法体作为参数,注意看此时fun()是谁调用的,没有谁调用它,所以也是走预编译,this依旧是window,打印222
4. b.say = a.say 将a的say方法赋给了b,然后b调用,this指向b,打印333
var a = 123;
function print() {
this.a = 234;
console.log(a);
}
print();
new print();
查看答案
234 123
1. 因为预编译this指向window,
print函数执行过程把GO的a替换成了234,所以打印234
2. new print() 相当于创建了基于print原型创建了this对象,this就等于new print(),a作为它属性,所以找GO上的a,打印123
var a = 5;
function test() {
a = 0;
alert(a);
alert(this.a);
var a;
alert(a);
}
请问运行 test()和运行 new test()结果分别是什么
查看答案
0,5,0
0,undefined,0
test()
1. 首先考虑预编译 GO{a:5,this:window}
2. test执行考虑函数预编译,AO{a: 0,this:window}
3. 继续执行 AO{a:0} 打印0
4. 打印5
5. 打印0
new test()
1. 首先考虑预编译 GO{a:5,this:window}
2. AO{a:undefined}
3. new test() = var this={__protp__:test.protptype},就是说此时test函数的this不指向window AO{a:undefined,this:{}}
4. 继续执行,AO{a:0},打印0
5. 打印undefined
6. 打印 0
var bar ={
a:"002"
}
function print() {
bar.a = "a"
Object.prototype.b = "b"
return function inner(){
console.log(bar.a)
console.log(bar.b)
}
}
print()()
查看答案
a,b
1. 预编译 GO{bar:{a:"002"}}
2. GO{bar:{a:"a"}
3. Object的原型上添加b属性值为b
4. 执行print函数,打印a,然后首先找bar的b,找不到,去原型上找,打印b
}
# 手写 call/bind/apply
实现 call 的原理: a.call(b,...args)
a 想要把 this 指向 b,就必须要将自己作为一个属性存入 b 对象,让 b 去调用这个属性的方法
var value = 1;
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
call()
改变了 this 的指向- 函数
bar
执行了
按照思路
在调用 call()的时候把函数 bar()
添加到 foo()
对象中
var foo = {
value: 1,
bar: function() {
console.log(this.value);
}
};
foo.bar(); // 1
这样就实现了 this 指向和调用问题,最后 delete 调多余的函数即可
1、将函数设置为对象的属性:foo.fn = bar
2、执行函数:foo.fn()
3、删除函数:delete foo.fn
代码实现
const mycall = function(context, ...args) {
context = context || window; //可能是全局,没传入的话
// 首先第一步获取调用方函数(this) 这里为了避免重名使用simbol
let caller = Simbol("caller");
context[caller] = this;
let res = context[caller](...args);
delete context.caller;
return res;
};
实现 apply
实现 apply 类似,知识传入的并不是所有参数列表,而是一个数组
const myapply = function(context, args) {
context = context || window; //可能是全局,没传入的话
// 首先第一步获取调用方函数(this) 这里为了避免重名使用simbol
let caller = Simbol("caller");
context[caller] = this;
let res = context[caller](...args);
delete context.caller;
return res;
};
推荐使用上述写法实现,这里如果在原型上写的话,没有明确第二个参数的话,arguments 的第一个是 this,记得最后执行函数过滤掉它,用
slice(1)
就可以
实现 bind
实现 bind 就更简单了,他和上面俩函数的区别在于他返回的是函数,并不是立即执行
Function.prototype.mybind = function() {
const args = Array.prototype.slice.call(arguments);
const t = args.shift();
const self = this;
return function() {
return self.apply(t, args);
};
};