JS 基础 — this 解析

this 是 JS 中常见的属性,但是经常会使人晕头转向,本文总结了一些常见的使用场景,希望能加深大家的认识。本文主要包含以下内容:

  1. this 常见场景
  2. 手动实现 call、bind

全局 this

  • 浏览器宿主的全局环境中,this 指向 window 对象
  • Node 命令行中,this 是全局命名空间,可以通过 global 来访问;在 Node 环境执行的 JS 脚本,this 是个空对象,不同于 global

函数方法里的 this

函数只是一个包含指针的变量名,它的结果根据执行上下文决定

函数中读写变量,是通过作用域链查找的。所以只要梳理出作用域链就能容易分辨出 this 指向

原型中的 this

同一函数创建的所有实例均共享一个原型。如果你给原型赋值了一个数组,那么所有实例都能获取到这个数组。除非你在某个实例中对其进行了重写(实事上是进行了覆盖)。

function Thing() {}

Thing.prototype.things = [];

var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");

console.log(thing2.things); //logs ["foo"]
复制代码

通常上面的做法是不正确的,改变 thing1 的同时也影响了 thing2。

function Thing() {
    this.things = [];
}

var thing1 = new Thing();
var thing2 = new Thing();
thing1.things.push("foo");

console.log(thing1.things); //logs ["foo"]
console.log(thing2.things); //logs []
复制代码

在JavaScript中,函数可以嵌套函数,也就是你可以在函数里面继续定义函数。但内层函数是通过闭包获取外层函数里定义的变量值的,而不是直接继承this。

function Thing() {}

Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
    var info = "attempting to log this.foo:";
    function doIt() {
        console.log(info, this.foo);
    }
    doIt(); // this 指向 window
}

var thing = new Thing();
thing.logFoo();  //logs "attempting to log this.foo: undefined"
复制代码

将实例的方法作为参数传递时,实例上下文并不会带过去。因为只是传递了一个函数的指针。

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {  
    console.log(this.foo);   
}

function doIt(method) {
    method(); // 方法调用时,this 都是指向上下文
}

var thing = new Thing();
thing.logFoo(); //logs "bar"
doIt(thing.logFoo); //logs undefined
复制代码

解决办法是在传递参数时,使用 bind 方法显示指明上下文

function Thing() {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () { 
    console.log(this.foo);
}

function doIt(method) {
    method();
}

var thing = new Thing();
doIt(thing.logFoo.bind(thing)); //logs bar
复制代码

call 实现

call 的作用是改变函数 this 的指向,调用函数并返回结果

Function.prototype.Call = function (context) {
  context = context || global;
  // this 指向 Call 的调用方,即函数 bar
  // step1:将函数 bar 作为 context 的属性,this 指向 context
  context.fn = this;
  let args = [];
  // arguments 和 this 是函数默认的两个属性
  // 第一个参数是 context,所以 i 从 1 开始
  for (let i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i]);
  }
  // step2:调用函数
  const res = context.fn(...args);
  // 如果不删除,会发生什么?
  delete context.fn;
  return res;
};

const bar = function (name, age) {
  this.name = name;
  this.age = age;
  console.log(this.name, this.age);
  console.log(this.value);
};

const foo = {
  value: 1
};

bar.Call(foo, "sam", 29);
// sam 29
// 1
复制代码

? 与 apply 的区别是从第二个参数开始的,call 的接受参数列表,apply 接受参数数组

bind 实现

bind 的作用

  • bind 改变函数的 this 指向
  • 返回新的函数,如果新函数是构造函数(被 new 调用),this 需要指向实例
  • 原型链的桥接

js-base-fn

Function.prototype.bindThis = function (context) {
  const self = this; // this 是 bindThis 的调用方,一般是函数
  const args = Array.prototype.slice.call(arguments, 1);
  // 更改 this
  const fn = function () {
    return self.apply(
      // 这里的 this 是实例
      // 新函数如果为 new 的构造函数,this 会有所不同
      // new 会将 this 指向新实例
      this instanceof fn ? this : context,
      args.concat(Array.prototype.slice.call(arguments))
    );
  };

  // 原型链桥接,并通过空函数防止原型链公用
  const fNOP = function () {};
  fNOP.prototype = this.prototype;
  fn.prototype = new fNOP();

  return fn;
};

function foo(a, b, c) {
  console.log("name: >>", this.name + a + b + c);
}

const bar = {
  name: "balabala"
};

foo.bindThis(bar)("变身", "美丽", "智慧")
// name: >> balabala变身美丽智慧
复制代码

小结

this 是一个非常“神奇”的属性,涉及到的知识面也很多,需要我们对原型,作用域等知识有清晰的认识。

如果你看完这篇文章仍然觉得很迷惑,别担心,这是正常的,这部分知识本来就比较抽象,需要你多看多练习,慢慢的就会理解 ?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享