近期在研究ES6Class
的时候,心血来潮,想看看国外是否有类似的文章来解释Class
对Prototype
进行糖化的。然后有一篇文件就映入眼帘。
里面有这样的一个细节剖析。大致的剧情如下:
我们为Canvas
创建一个Cricle
类,这个类大致能做
- 1. 能够计算
Circle
被实例化几次 - 2. 能够随意设置
Circle
实例的半径和查询对应的半径数值 - 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. 没有引入
flag
如何实现计算Circle
被实例化多少次 - 2.
Object.defineProperty
里面的this
是指向了who - 3. ES6的
Class
是如何对Prototype
进行优雅的糖化的 - 4. 为什么ES6的
Class
(它本身就是一个函数,并且还是一个构造函数)不能直接调用,但是ES5却可以 - 5. 还有很多,以后再说....
让我们就开始惨案的解密过程吧。 首先,我们不按常规去思考上面的疑惑,我们需要在破案之前,需要一些准备工具。 首先让人很刺眼的一个是: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一步一步涨上去的。
- 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
的用途只有一个生成实例。虽然他是函数。并且是构造函数,没办法,实力不允许它去直接调用。
想调用可以,控制台飘红。