由一篇ES6-Class科普文章引起的“惨案”

近期在研究ES6Class的时候,心血来潮,想看看国外是否有相似的文章来解释ClassPrototype进行糖化的。而后有一篇文件就映入眼帘。javascript

里面有这样的一个细节剖析。大体的剧情以下:html

咱们为Canvas建立一个Cricle类,这个类大体能作java

  1. 可以计算Circle被实例化几回
  2. 可以随意设置Circle实例的半径和查询对应的半径数值
  3. 能计算这个圆的面积

Talk is cheap ,show you the code:es6

function Circle(radius) {
    this.radius = radius;
    Circle.circlesMade++;
}

Object.defineProperty(Circle, "circlesMade", {
    get: function() {
        return !this._count ? 0 : this._count;
    },

    set: function(val) {
        this._count = val;
    }
});

Circle.prototype = {
    area: function area() {
        return Math.pow(this.radius, 2) * Math.PI;
    }
};

Object.defineProperty(Circle.prototype, "radius", {
    get: function() {
        return this._radius;
    },

    set: function(radius) {
        if (!Number.isInteger(radius))
            throw new Error("Circle radius must be an integer.");
        this._radius = radius;
    }
})
复制代码

若是熟悉ES5语法的童鞋说,这鸡毛代码,有啥,不就是定义了一个Circle类,而后实现了一些方法吗。bash

可是我想说的是,这个题目的大体提纲就是这个。可是有一个点,很让人费劲。可以计算Circle被实例化几回。其实在通常开发中,若是遇到这个问题,第一反应就是,要想计算某一个东西被操做了几回,用一个全局flag实现不就好了。(个人第一反应也是这个)。可是看到上面的代码以后,发现本身仍是太年轻。有捷径不走,非要从绕远。(脑子瓦塔了)函数

其实上面的例子是用来说述:ES6的Class是如何优雅的进行代码书写。 可是我在看彻底文的时候,其实并不关心优雅的结果。其实我关心的是,这玩意儿是如何实现的。若是你们想看美美哒的代码实现,能够先移步到原文进行对美的观摩。可是不要忘记回来,听我继续唠叨。工具

从上面的例子中,我有几点比较感兴趣(好奇害死猫,我头发上的Tony又风雨飘摇了,由于我又要熬夜了)oop

  1. 没有引入flag如何实现计算Circle被实例化多少次
  2. Object.defineProperty里面的this是指向了who
  3. ES6的Class是如何对Prototype进行优雅的糖化
  4. 为何ES6的Class(它自己就是一个函数,而且仍是一个构造函数)不能直接调用,可是ES5却能够
  5. 还有不少,之后再说....

让咱们就开始惨案的解密过程吧。 首先,咱们不按常规去思考上面的疑惑,咱们须要在破案以前,须要一些准备工具。 首先让人很刺眼的一个是:Object.defineProperty。既然遇到了,咱们就来会会他。post

Object.defineProperty

该方法是用于对指定对象进行自定义属性的赋值。具体公式Object.defineProperty(obj, prop, descriptor)。也就是说,若是想为一个对象定义一个属性,用这个很好用(固然也能够直接字面量),同时还能够进行configurableenumerable等属性的配置。若是想了解更多,能够直接参考MDN的相关介绍。 若是你查看的比较细致的话,其实第三个参数descriptor是一个针对须要设置属性的描述性对象信息。其中有一段话,颇有意思。 ui

简单解释一下就是, get()/set()这两个可选函数中的 this的指向就是, 谁访问了被descriptor描述的属性,这个this就指向谁(可是若是涉及到继承,那就状况不同了)。这和函数的 this指向的机制是同样的。那很瓜熟蒂落,上面的第二个 谜团解开了。

this===Circle

若是对函数中this还不是很了解,能够先移步理解JS函数调用和"this"。(明白了以后,记得继续看破案过程哈,很赤鸡的)

而后,咱们既然已经有了点眉目了,让咱们继续快马加鞭的寻找下一个受害者

没有引入flag如何实现计算Circle被实例化多少次

不知道你们,对这个问题如何看待,反正我是第一次遇到这种代码(不新增全局flag来计算构造函数被实例化多少次)

我喜欢挑战,那咱们就迎难(男)而上吧。

首先,须要明确的一点就是,咱们是须要实例化一个Circle类。而用ES5去实现一个'类',其实很机械的就是以下的模板:

var C = function (x,y){
    this.x = x;
    this.y = y;
}
C.prototype = {
    constructor:C
    toStirng:function(){
        return '北宸南蓁'
    }
}
    
}
复制代码

Note:上面有一个在进行prototype赋值的时候,多写了一行,这个在有些状况下很重要。具体缘由

那咱们分析一下Circle的实现

function Circle(radius) {
    this.radius = radius;
    Circle.circlesMade++;
}
复制代码

看起来很平淡无奇,可是若是细心的童鞋就会发现。

咦。咦,咦。咋和上面的那个模板有一丢丢的区别。其实就是这么一丢丢的区别,致使了质的飞越。

搬一个小板凳一块儿研究一下。

首先,咱们须要回顾一下ES5或者是ES6实例化一个类时。是否是常常挂在嘴边的话。 在进行new的时候,会自动触发构造函数。同时将this指向哪里....等等的样板术语。

而后咱们来模拟一下如何实例化一个对象。(这里咱们用伪代码)

var instance1 = new Circle();
new 里面发生了不少事情,
1. 建立一个空对象,做为将要返回的对象实例。
2. 将这个空对象的原型,指向构造函数的prototype属性。
3. 将这个空对象赋值给函数内部的this关键字。
4. 开始执行构造函数内部的代码。
复制代码

其实咱们很关心最后一步,开始执行构造函数内部代码。在Circle中有一个很扎眼的代码Cricle.circlesMade++,将它更加简便一点就是Cricle.circlesMade=Cricle.circlesMade+1

也就是说每次在进行一次Circle实例化的时候,Cricle.circlesMade的数值好像都增长1。不是好像,确实就是每次加1。关键这个circlesMade他的级别还很高,是个王者段位,它比永恒砖石radius的级别都高。由于他是挂载在Cricle对象上的。(毕竟人家是人民币玩家,V8)

而后咱们继续来分析,上面的分析中了解到,每次实例化都加1,可是这个王者段位circlesMade初始段位0是0啊,还有它是如何一步一步,从最低段位艰难的爬到最强王者的。

其实结合上面讲到的Object.defineProperty很容易了解到,原来circlesMade这哥们,也是从白银这个初始段位0一步一步涨上去的。

  1. 在进行初次实例化的时候,会进行Cricle.circlesMade++,而这个操做能够前后分为取值(get)/赋值(set),而这些操做的使用说明书就在以下代码中。在第一次进行get的时候,会有一个判断!this._count ? 0 : this._count而咱们在分析Object.defineProperty的时候,就讲到过里面的this指向问题。
    get()/set()这两个可选函数中的this的指向就是:谁访问了被descriptor描述的属性,这个this就指向谁(为了避免让大家向上找,我CV过来了,有点贴心有木有)
    因此,如今这里的this就是Cricle,也就是说在进行取值(get)的时候,会进行一次三元判断,若是没有,那就是新赛季刚开始,有一个初始值(0)。若是原来已经有值了,那就是在原有段位上,继续上分。
  2. 在进行到赋值(set)的时候,其实就是直接将Cricle.circlesMade+1做为val赋值给this._count.
  3. 而后每次新建实例的时候,都是运行1-2的步骤。
Object.defineProperty(Circle, "circlesMade", {
    get: function() {
        return !this._count ? 0 : this._count;
    },

    set: function(val) {
        this._count = val;
    }
});
复制代码

Note:若是有些童鞋,对new是如何进行对象的构建和ES5是如何基于Prototype进行继承的。能够参考理解JS中的原型(Prototypes)

ES6的Class是如何对Prototype进行优雅的糖化

在写的时候,发现这是一个比较有趣的问题,我选择再写一篇相关文件,进行详细的说明,这里就不说了。

天不早了我们找个酒店聊会儿天吧。-----郭德纲

而后大家想要的番外篇-ES6-Class如何优雅的进行Prototype“糖化”他来了。

为何ES6的Class(它自己就是一个函数)不能直接调用

人狠话很少,直接进入正题。 定义一个最最最简单的ES6的Class

class A {

}
复制代码

守得云开见日月

"use strict";

function _instanceof(left, right) {
  if (
    right != null &&
    typeof Symbol !== "undefined" &&
    right[Symbol.hasInstance]
  ) {
    return !!right[Symbol.hasInstance](left);
  } else {
    return left instanceof right;
  }
}

function _classCallCheck(instance, Constructor) {
  if (!_instanceof(instance, Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var A = function A() {
  _classCallCheck(this, A);
};

复制代码

咱们来简单的剖析一下啊。速度,用小本本记录一下哈。

  1. ES6默认开启strict
  2. Symbol.hasInstance
    对象的Symbol.hasInstance属性,指向一个内部方法。当其余对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。好比,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。

千言万语汇成一句话就是:ES6中的Class的用途只有一个生成实例。虽然他是函数。而且是构造函数,没办法,实力不容许它去直接调用。

想调用能够,控制台飘红。

相关文章
相关标签/搜索