共计 3898 个字符,预计需要花费 10 分钟才能阅读完成。
前言
工作中有很多用到 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