# 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
  1. call()改变了 this 的指向
  2. 函数 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);
  };
};
Last update: 7/20/2021, 8:53:54 AM