近期在研究ES6Class的时候,心血来潮,想看看国外是否有类似的文章来解释Class对Prototype进行糖化的。然后有一篇文件就映入眼帘。
里面有这样的一个细节剖析。大致的剧情如下:
我们为Canvas创建一个Cricle类,这个类大致能做
- 能够计算
Circle被实例化几次 - 能够随意设置
Circle实例的半径和查询对应的半径数值 - 能计算这个圆的面积
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又摇摇欲坠了,因为我又要熬夜了)
- 没有引入
flag如何实现计算Circle被实例化多少次 Object.defineProperty里面的this是指向了who- ES6的
Class是如何对Prototype进行优雅的糖化的 - 为什么ES6的
Class(它本身就是一个函数,并且还是一个构造函数)不能直接调用,但是ES5却可以 - 还有很多,以后再说....
让我们就开始惨案的解密过程吧。
首先,我们不按常规去思考上面的疑惑,我们需要在破案之前,需要一些准备工具。
首先让人很刺眼的一个是:Object.defineProperty。既然遇到了,我们就来会会他。
Object.defineProperty
该方法是用于对指定对象进行自定义属性的赋值。具体公式Object.defineProperty(obj, prop, descriptor)。也就是说,如果想为一个对象定义一个属性,用这个很好用(当然也可以直接字面量),同时还可以进行configurable、enumerable等属性的配置。如果想了解更多,可以直接参考MDN的相关介绍。
如果你查看的比较细致的话,其实第三个参数descriptor是一个针对需要设置属性的描述性对象信息。其中有一段话,很有意思。
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一步一步涨上去的。
- 在进行初次实例化的时候,会进行
Cricle.circlesMade++,而这个操作可以先后分为取值(get)/赋值(set),而这些操作的使用说明书就在如下代码中。在第一次进行get的时候,会有一个判断!this._count ? 0 : this._count而我们在分析Object.defineProperty的时候,就讲到过里面的this指向问题。get()/set()这两个可选函数中的this的指向就是:谁访问了被descriptor描述的属性,这个this就指向谁(为了不让你们向上找,我CV过来了,有点贴心有木有)
所以,现在这里的this就是Cricle,也就是说在进行取值(get)的时候,会进行一次三元判断,如果没有,那就是新赛季刚开始,有一个初始值(0)。如果原来已经有值了,那就是在原有段位上,继续上分。 - 在进行到
赋值(set)的时候,其实就是直接将Cricle.circlesMade+1作为val赋值给this._count. - 然后每次新建实例的时候,都是运行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);
};
我们来简单的剖析一下啊。速度,用小本本记录一下哈。
- ES6默认开启
strict - Symbol.hasInstance
对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。
千言万语汇成一句话就是:ES6中的Class的用途只有一个生成实例。虽然他是函数。并且是构造函数,没办法,实力不允许它去直接调用。
想调用可以,控制台飘红。