likes
comments
collection
share

How JavaScript Works 学习笔记 —— How this Works

作者站长头像
站长
· 阅读数 0

Self 首先将 class 替换成了 prototypes。这样 object 就可以直接继承另一个 object 而不需要通过 class 来实现。某种程度上是对于 class 模型的一种简化。

这里不得不吐槽,我们却又在不厌其烦的研究,JS 中 class 的不同写法,甚至催逼 ES6 出了 class 这样一个语法糖的实现,确实是一个有意思的事情。至少在作者看来,是有点画蛇添足。

还是简单的介绍一下 prototype 的原理

  • objects => containers of properties
  • prototypes => objects
  • methods => functions stored in objects 当我们从 object 中尝试区某个 property 的时候,如果有,就直接返回,如果没有则会尝试从当前 object 的 prototype 中取。如果 prototype 中也没有,因为 prototype 本身也是一个 object 则会继续网上直到取到 property 或者不存在 prototype。

一个非常简单的递归。

为什么要使用 prototype?

那么,为什么我们要将一部分的 property 放在 prototype 而不是 object 本身呢。其实很简单,为了共享一部分数据。假设,所有 person 都有,walk,speak 的方法,我们没有必要为所有的 person 都定义一遍。我们就可以将可以共享的部分放在一个 object 做,其他要使用的 object 直接通过 prototype 访问就可以了。

const person = {
  walk: function() {},
  speak: function() {
    console.log(`My name is ${this.name}`);
  }
}

const zhangsan = Object.create(person);
zhangsan.name = "zhangsan";

const jack = Object.create(person);
zhangsan.name = "jack";

这样,就可以完全不需要借助 class 而实现 object 中逻辑的复用。而实际上,prototype 最常见的作用,就是用来存放与状态无关的 methods。

下面我们聊聊 this

我们继续看上面的例子,当我们调用 zhangsan.speak的时候,speak 如何知道它要操作的对象是什么。因为 speak 可能在 prototype chain 中,就可能定义在完全不同的 object 中。speak 又如何能够读到我们认知的调用者 zhangsan 上面的状态呢。

// zhangsan
{
  name: 'zhangsan'
}

======================================

// person
{
  walk,
  speak,
}

答案,当然就是 this。这样,我们完全不用在乎 speak 是在 object 上还是在 object 的 prototype 上,甚至是在 prototype 的 prototype 中。我们只需要在乎,在调用 zhangsan.speak 时,speak 存在或者不存在即可。至于它存在于哪一层,甚至定义在哪里,都无关。

this 只存在于 method 中

之前我们聊过,当,function 作为 object 的属性的时候,我们称之为 method。也就是说,method 有调用者,但是 function 没有。也就是说,this 永远指向 method 的调用者

const speak = zhangsan.speak;
speak()

如上,当我们把 speak 变成一个普通 function 时,this 就失去了意义。(非 strict 模式,this 指向 window | global, strict 模式指向 undefined)。当然,我们也可以手动为 function 指定调用者。

speak.bind(zhangsan)();
// or 
speak.call(zhangsan)
// or 
speak.apply(zhangsan)

this 的神奇之处在于,它跟 method 调用的方式有关。而不是像其他变量一样,当变量定义的时候,它的值就已经确定了。这也是,对于初学者来说,很难理解,经常出错的部分。举个非常常见的例子:

function Person() {
    this.speak = function (word) {
        console.log(word);
    }

    this.sayHello = function () {
        ['h', 'e', 'l', 'l', 'o'].forEach(function (w) {
            this.speak(w);
        });
    }
}

就会发现 this 上不存在 speak,原因是因为 forEach 的回调函数是由 array 调用的,这样 this 跟外部的 this 其实是不一致的。这与我们认知的在同一个区域(function or block)中相同变量,会有不同的含义。

当我们使用 new 的时候,发生了什么

对于一个普通的 function, 本质上,它存在两个角色:

  • function 被直接调用
  • 调用 function 时,使用 new,作为 构造函数使用 某种程度上说,new 类似一个语法糖,我们看看,new 做了些什么:
  • 根据当前构造函数的 prototype 创建一个空对象,并赋值给 this, var this = Object.create(prototype);
  • 将 this bind 到 function,并且运行 function
  • 如果 function 本身没有 return 值,将 this 作为默认 return 值 这样,我们就可以手动实现一个 new 函数
function newFun(constructorFun, arg) {
  var tempObj = Object.create(constructorFun.prototype);
  return (constructorFun.apply(tempObj, arg) || tempObj);
}

当然,既然每个函数都可以作为构造函数使用,我们有一个不成文的约定:只有构造函数是大写字母开头,其他都是小写字母开头。

this free

不得不说,在 JS 很大一部分问题都出自 this。在 JS 中,因为我们过于依赖 class based 语言中对于 class 和 this 的理解,常常会产生各种各样的问题。至少作者对于,this, class 都是持保留态度的。JS 中如果没有 this, 依然是图灵完备的。

我自己也在工作中开始尝试尽可能不写 class / 构造函数,不写 this,是另外一种体验。就像 React 也开始放弃 class based component 而转向 function based component。就像 React 与 Angular 的区别,我想 JS 本身的一大魅力也在于,你可以完全倒向 class based 也可以倒向另外一个方向。

以上。