前言
工作中有很多用到Class类的地方,通常是暴露出class类,或者暴露Class类的实例,比如商品有价格、规格、名称等属性,就可以通过new一个Class类来初始化商品对象。
对es6有了解的朋友应该知道es6是es5的语法糖,Class类实际上是基于es5的构造函数实现的生成实例对象的方法,但是Class类的写法更优雅,更趋近于传统的面向对象编程。
虽说平时常用Class类,可是也常会忽略掉细节而导致问题,出于好奇和为了彻底理解Class类,我利用babel工具降级es6语法,看看Class类的“庐山真面目”,以及理解babel到底做了什么,写文记录以供自己复习。
一个Class类babel前后
定义一个Class类
class K {
constructor(name) {
this.name = name;
}
// 静态方法
static classMethod() {
this.getname()
return 'hello';
}
// setter
set prop(value) {
console.log('setter: '+ value);
}
// getter
get prop() {
return 'getter';
}
// 原型方法
getName() {
return "celeste";
}
}
let k = new K("celeste")
上面的类利用babel降级语法后代码如下:
var K = function () {
function K(name) {
_classCallCheck(this, K);
this.name = name;
}
_createClass(K, [{
key: 'getName',
value: function getname() {
return "celeste";
}
}, {
key: 'prop',
set: function set(value) {
console.log('setter: ' + value);
},
get: function get() {
return 'getter';
}
}], [{
key: 'classMethod',
value: function classMethod() {
this.getName();
return 'hello';
}
}]);
return K;
}();
var k = new K("celeste");
可以发现Class类本质上是个自执行函数。这个函数执行完毕返回一个构造函数K。
并且,这里定义函数不是用函数声明的形式,而是用变量声明赋值var K,这其实就是class类不存在变量提升的原因,因为虽然js函数会先扫描整个函数体语句,将所有声明的变量提升到函数的顶部,但是不会提升赋值,在console前变量K还未赋值所以打印结果是undefined。
// 变量赋值
console.log(Bb); // undefined
var Bb = function Bb () {};
// 函数声明
console.log(Aa); // ƒ Aa () {}
function Aa () {};
_classCallCheck、_createClass函数
看完外层,再看看里面的关键信息,主要看_classCallCheck、_createClass他们做了什么,源码如下:
"use strict";
// 为了向前兼容,es6语法实际上是严格模式的
// 类or模块中只有严格模式可用
// 判断right是否为left的构造函数
function _instanceof(left, right) {
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}
// 判断Constructor是否instance的构造函数,如果不是则抛出错误
function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
// 遍历props,设置props里每一项的属性并挂载到target上
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
// 定义是否可枚举(否)
descriptor.enumerable = descriptor.enumerable || false;
// 定义是否可删除(可)
descriptor.configurable = true;
// descriptor有value属性的话(即除了set/get外的原型方法),可赋值
if ("value" in descriptor) descriptor.writable = true;
// 将变量descriptor.key定义到target上
Object.defineProperty(target, descriptor.key, descriptor);
}
}
// 参数分别是:构造函数、原型方法、静态方法
function _createClass(Constructor, protoProps, staticProps) {
// 原型方法挂载到构造函数的原型上
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
// 静态方法(用了static关键字定义的函数)会作为第三个参数数组里的项传进来,会直接成为构造函数下的一个属性
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
所以constructor事实上就是初始化了一个构造函数:
function K(name) {
_classCallCheck(this, K);
this.name = name;
}
_classCallCheck(this, K)的作用就是判断K是否为this的构造函数,不是的话抛出错误,确保万无一失(依据是如果K是个构造函数那么this一定是指向K的实例对象的)。
而_createClass函数的作用是就是将定义在类里的方法挂载到函数的原型(针对原型方法)或者类本身(针对static静态方法)上:
把_createClass函数与函数调用直观地放在一起看:
// _createClass函数
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
// 语法降级后自执行函数里的函数执行
_createClass(K, [{
key: 'getName',
value: function getname() {
return "celeste";
}
}, {
key: 'prop',
set: function set(value) {
console.log('setter: ' + value);
},
get: function get() {
return 'getter';
}
}], [{
key: 'classMethod',
value: function classMethod() {
this.getName();
return 'hello';
}
}]);
可以看到,setter、getter也在第二个参数数组里,他们也是原型上的方法,传参时有些许不同,value —— set/get,是为了在挂载到原型上的时候加以区分的,把他们区分开的代码就是_defineProperties函数里的这句话:
if ("value" in descriptor) descriptor.writable = true;
一些结论:
1.类的所有方法都定义在类的prototype
属性上面
所以类的新方法可以利用`Object.assign`添加在`prototype`对象上面
Object.assign(Person.prototype, {
// add some functions ...
});
2.类的内部所有定义的方法,都是不可枚举的(non-enumerable)
3.js引擎会自动为空的类添加一个空的constructor
方法(事实上就是会默认创建一个构造函数,将构造函数的this指向类的实例)
4.constructor
函数可以return Object.create(null)返回一个全新的对象,可导致实例对象不是类的实例。
下一节再看剩余的问题啦,这两天总结完毕或许会合并成一篇也可能新开一篇...——2020/06/06 01:20
小贴士:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
babel在线工具 https://babeljs.io/repl
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。