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

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

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

里面有这样的一个细节剖析。大致的剧情如下:

我们为Canvas创建一个Cricle类,这个类大致能做

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

Talk is cheap ,show you the code:

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类,然后实现了一些方法吗。

但是我想说的是,这个题目的大致提纲就是这个。但是有一个点,很让人费劲。能够计算Circle被实例化几次。其实在一般开发中,如果遇到这个问题,第一反应就是,要想计算某一个东西被操作了几次,用一个全局flag实现不就行了。(我的第一反应也是这个)。但是看到上面的代码之后,发现自己还是太年轻。有捷径不走,非要从绕远。(脑子瓦塔了)

其实上面的例子是用来讲述:ES6的Class是如何优雅的进行代码书写。 但是我在看完全文的时候,其实并不关心优雅的结果。其实我关心的是,这玩意儿是如何实现的。如果大家想看美美哒的代码实现,可以先移步到原文进行对美的观摩。但是不要忘记回来,听我继续唠叨。

从上面的例子中,我有几点比较感兴趣(好奇害死猫,我头发上的Tony又摇摇欲坠了,因为我又要熬夜了)

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

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

Object.defineProperty

该方法是用于对指定对象进行自定义属性的赋值。具体公式Object.defineProperty(obj, prop, descriptor)。也就是说,如果想为一个对象定义一个属性,用这个很好用(当然也可以直接字面量),同时还可以进行configurableenumerable等属性的配置。如果想了解更多,可以直接参考MDN的相关介绍。 如果你查看的比较细致的话,其实第三个参数descriptor是一个针对需要设置属性的描述性对象信息。其中有一段话,很有意思。61DEA8D1-B85D-46B5-AB2F-3FD8DD83284A.png简单解释一下就是,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. 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. 2. 在进行到赋值(set)的时候,其实就是直接将Cricle.circlesMade+1作为val赋值给this._count.
  3. 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. 1. ES6默认开启strict
  2. 2. Symbol.hasInstance
    对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。

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

想调用可以,控制台飘红。

相关文章
|
6月前
|
前端开发 JavaScript Java
ES6前端就业课第三课之class
ES6前端就业课第三课之class
64 0
|
JavaScript 前端开发
细读 ES6 | Class 下篇
ES6 中的继承。
134 0
|
存储 监控 JavaScript
保熟的TS知识,拜托,超快超酷的好吗
这一步对于很多人来说是最简单的一步,也是最难的一步,说简单是因为这确确实实仅是入门的一步,就是一个环境配置,说难则是因为很多人无法跨出这一步,当你跨出这一步之后,你会发现后面的真的学得很快很快,现在,就让我们一起跨出这一步吧~
70 0
|
JSON 算法 JavaScript
喜大普奔,es2019登场
就在刚4个小时前,TC39将以下特性加入到了 ES2019 中。让我们来看看这些新的特性给我们带来了什么样的改变。
喜大普奔,es2019登场
|
JavaScript 前端开发 安全
细读 ES6 | Class 上篇
今天仔细看下 ES6 中的 Class 语法。
155 0
细读 ES6 | Class 上篇
|
存储 前端开发 JavaScript
手牵手🧑‍🤝‍🧑学习Gulp不用愁
手牵手🧑‍🤝‍🧑学习Gulp不用愁
手牵手🧑‍🤝‍🧑学习Gulp不用愁
|
Java 程序员
漫画:Object类很大,你忍一下(完结篇)
这一次,我们来重点讲解 wait(),notify(),notifyAll() 这三大方法。
173 0
|
消息中间件 JavaScript 前端开发
炸裂!手摸手教你如何吃透一个 Java 项目,yyds
炸裂!手摸手教你如何吃透一个 Java 项目,yyds
218 0
炸裂!手摸手教你如何吃透一个 Java 项目,yyds
|
弹性计算 负载均衡 关系型数据库
Class 7 参营总结与感想
Class 7 参营总结与感想