《你不知道的Javascript》阅读笔记之this全面解析

2020-10-01  本文已影响0人  荔桑

任何先进的技术都和魔法无异。
--Arthur C.Clarke

首先函数的this是在调用时被绑定的,完全取决于函数的调用位置,找到调用位置以后,需要判断应用下面四条规则中的哪一条。
1.默认绑定

function foo() {
  console.log(this.a)
}
var a = 2;
foo(); // 2

函数直接使用不带任何修饰符的函数引用进行调用,只能使用默认绑定
如果是使用严格模式,全局对象无法使用默认绑定

function foo() {
    "use strict";
  
  console.log(this.a)
}
var a = 2;
foo(); // TypeError: this is undefined

2.隐式绑定

function foo() {
  console.log(this.a)
}
var obj = {
  a: 2,
  foo: foo
};
obj.foo() // 2

存在问题:隐式丢失

function foo() {
  console.log(this.a)
}
var obj = {
  a: 2,
  foo: foo
};

var bar = obj.foo; //bar是obj.foo的一个引用,引用的foo函数本身

var a = "opps,global"; // 全局对象的属性

bar() // "opps,global" 
// 执行一个不带任何修饰的函数调用,应用默认绑定

3.显式绑定
使用apply(...)方法和call(...)方法可以直接指定this的绑定对象

function foo() {
  console.log(this.a)
}
var obj = {
  a : 2
}
foo.call(obj); // 2

(1).显示绑定的变种---硬绑定

function foo() {
  console.log(this.a)
}
var obj = {
  a : 2
}
var bar = function() {
  foo.call(obj)
}
bar();  // 2
setTimeout(bar,100) // 2
bar.call(window); // 2
//无论怎么调用bar,都会手动在obj上调用foo,这是硬绑定

硬绑定的应用场景:

//创建一个包裹函数
function foo(something) {
  console.log(this.something);
  return this.a + something
}
var obj = {
  a : 2
}
var bar = function() {
  return foo.apply(obj,arguments);
}
var b = bar(3);  // 2  3
console.log(b); // 5
//更高级用法是创建一个可以重复使用的辅助函数
function foo(something) {
  console.log(this.something);
  return this.a + something
}
function bind(fn, obj) {
  return function() {
    return fn.apply(obj,arguments);
  }
}
var obj = {
  a : 2
}
var bar = bind(foo,obj);
var b = bar(3); // 2  3
console.log(b); // 5

(2).API调用上下文参数

// JS内置函数提供一个可选参数“context”
function foo(el) {
  console.log(el,this.id)
}
var obj = {
  id : "awesome"
}
[1,2,3].forEach(foo,obj);

4.new绑定
传统的面向对象语言使用new初始化类时会调用类中的构造函数,javascript中的new来调用函数,会执行以下操作
(1)创建(或者构造)一个全新的对象
(2)这个新对象会被执行原型链接
(3)这个新对象会绑定到函数调用的this
(4)如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

function foo(a) {
  this.a = a
}
var bar = new foo(2);
console.log(bar.a); // 2
// 使用new来调用foo(...)时,会构造一个新对象并把它绑定到foo(...)调用中的this上

函数调用位置的优先级如下:
new>显>隐>默认
一般情况下遵循以上规则,也有绑定例外情况:
(1)使用null或者undefined作为this的绑定对象传入call,apply或者bind,这些在调用时会被忽略,实行默认绑定原则:

function() {
  console.log(this.a)
}
var a = 2;
foo.call(null); // 2

为了更安全的使用this应该用Object.create(null)

function foo(a,b) {
  console.log("a:" + a + ",b" + b);
}
var kong = Object.create(null);
foo.apply(kong,[2,3]);  // a:2,b:3

var bar = foo.bind(kong,2);
bar(3); // a:2,b:3

(2)间接引用

function foo() {
  console.log(this.a);
}
var a = 2;
var o = { a: 3, foo: foo};
var p = { a: 4}

o.foo(); // 3
(p.foo = o.foo)();  // 2  全局
// 赋值表达式p.foo = o.foo的返回值是目标函数的引用,
// 因此调用位置是foo()而不是p.foo或者o.foo

// 队遇默认绑定来说决定this绑定对象的并不是调用位置是否处于严格模式而是函数体是否处于严格模式
// 如果函数体处于严格模式,this会被绑定到undefined,否则全局

(3)软绑定
实现和硬绑定相同的效果同时保留隐式绑定或者显示绑定修改this的能力

if(!Function.prototype.softBind) {
  Function.prototype.softBind = function(obj) {
    var fn = this;
    var curried = [].slice.call(arguments,1);
    var bound = function() {
      return fn.apply(
        (!this || this === (window || global)) ? 
           obj : this
        curried.concat.apply(curried,arguments)
      )
    }
    bound.prototype = Object.create(fn.prototype);
    return bound;
  }
}
function foo() {
  console.log("name:" + this.name);
}
var obj = { name: "obj" },
    obj2 = { name: "obj2" },
    obj3 = { name: "obj3" };
var fooOBJ = foo.softBind(obj);
fooOBJ();  // name:obj

obj2.foo = foo.softBind(obj);
obj2.foo();  // name:obj2

fooOBJ.call(obj3);  // name:obj3
setTimeout(obj2.foo,10);  // name:obj

(4)箭头函数

function foo() {
  return (a) => {
    console.log(this.a)
  }
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };

var bar = foo.call(obj1);
bar.call(obj2); // 2
//foo()内部创建的箭头函数会捕获调用时foo()的this,绑定到obj1后无法修改new也不行

总结
判断一个运行函数的this绑定,就要找到这个函数的直接调用位置,然后按照new>显>隐>默认顺序应用这四条规则来判断this的绑定对象
箭头函数不使用这四条规则,而是根据词法作用域来决定this

上一篇下一篇

猜你喜欢

热点阅读