最近被裁员了,所以重新复习和理解一下。 加深印象的同时,减少恐慌感 -_-!!
先写个 案例
class Person {
name
static age
#sex
constructor(name, age, sex) {
this.name = name
Person.age = age
this.#sex = sex
}
sayName () { console.log(this.name) }
sayAge () { console.log(Person.name) }
saySex () { console.log(this.#sex) }
}
var tom = new Person('tom', 12, '男')
console.log(tom) // { name: 'tom' }
console.log(tom['__proto__']) // {}
console.log(Object.getOwnPropertyNames(tom['__proto__'])) // [ 'constructor', 'sayName', 'sayAge', 'saySex' ]
tom.sayName() // 'tom'
tom.sayAge() // 12
tom.saySex() // '男'
上面案例,里面有 static, #, constructor, new, 还有一个隐藏的 public
先简单介绍一下每个关键字代表的意思
- constructor:构造函数,class 最基础的元素,如果在 class 未声明 constructor, class 会默认自动带出
- public:公有字段,定义在 this 或 constructor.prototype
- static:静态字段,定义在 constructor
- private: 私有字段,提案阶段,只能 类 自身调用
- protected: 保护字段,es6暂没实现
- super:子类的构造函数中代表 父类的构造函数,普通方法中代表 父类的原型对象,静态方法中代表 父类
- new: 创建实例
先从 class 开始讲
class 是什么,class 是个语法糖 简单的转化一下
class Person {
constructor (name) {
this.name = name;
}
sayHi() {}
}
///////转换成////////
var Person = function (name) {
this.name = name
}
Person.prototype.sayHi = function() {}
那为什么要这样写
- 更好的范式(工厂模式)
- 为了让 java 和 C++ 的程序员 更好的卷前端,狗头
class 跟 传统的构造函数生成实例,还是有些许差别的
- class 只能 new,不能像普通函数调用
- class 里面的所有定义的方法,都是不可枚举的
- 严格模式
- 保留一个私有作用域给 class, 为 private 作准备
手写一下 class
按上面的描述,手写一下 class
将上面的不同点,往里面塞
案例
class Person {
name
static age
#sex
constructor(name, age, sex) {
this.name = name
Person.age = age
this.#sex = sex
}
sayInfo () { console.log(this.name,Person.age,this.#sex) }
}
简单实现
var _sex = Symbol();
var Person = function(name,age,sex) {
this.name = name;
Person.age = age;
this[_sex] = sex; // 只能模拟实现private,不能完全实现,getOwnPropertySymbols可以获取
}
Person.prototype.sayInfo = function() { console.log(this.name,Person.age,this[_sex]) }
将不同点塞入实现
"use strict"; // 严格模式
var Person = (function() {
// 保留一个私有作用域
const _sex = Symbol();
function Person(name,age,sex) {
// 只能用new
if (!(this instanceof Person) throw new TypeError('must be new')
this.name = name;
// 将 sayInfo 方法 塞进 prototype里,并且是 不可枚举
Object.defineProperty(Person.prototype, 'sayInfo', {
enumerable:false,
value: function() {
console.log(this.name,Person.age,this[_sex])
}
})
// 将 age 塞到 构造函数里, 并且也是 不可枚举
Object.defineProperty(Person, 'age', {
enumerable: false,
value: age
})
// 将 sex 塞到 this 里
Object.defineProperty(this, _sex, {
enumerable: false,
value: sex
})
]
return Person;
})()
优化一下, 将公共的提取出来
"use strict";
// 创建 class
var _createClass = (function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configrable = true
if ('value' in descriptor) {
descriptor.writable = true
}
Object.defineProperty(target, descriptor.key, descriptor)
}
}
return function(Constructor, instanceProps, protoProps, staticProps, privateProps) {
if (instanceProps)
defineProperties(this, instanceProps)
if (protoProps)
defineProperties(Constructor.prototype, protoProps)
if (staticProps)
defineProperties(Constructor, staticProps)
if (privateProps)
defineProperties(this, privateProps)
}
})()
// 判断是否是new创建
function _checkNew(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('must be new')
}
}
var Person = (function () {
var _sex = Symbol();
function Person (name, age, sex) {
_checkNew(this, Person);
_createClass.call(this, Person, [{
key:'name',
value: name,
enumerable: true
}], [{
key: 'sayInfo',
value: function () {
console.log(this.name,Person.age,this[_sex])
}
}], [{
key: 'age',
value: age
}], [{
key: _sex,
value: sex
}])
}
return Person;
})()
var tom = new Person('tom', 12, '男')
console.log(tom) // { name: 'tom' }
console.log(tom['__proto__']) // {}
console.log(Object.getOwnPropertyNames(tom['__proto__'])) // [ 'constructor', 'sayInfo']
tom.sayInfo() // tom 12 男
再手写一个 new
这个比较简单了 new 的作用,创建出来的实例可以访问到构造函数中的属性 new 做了什么
- 生成空对象
- 将空对象的原型指向构造函数的原型对象
- 构造函数在空对象的环境执行
- 如果返回值为对象,返回 返回值,否则返回前面的空对象
function _new(...args) {
const fn = args.shift();
const context = Object.create(fn.prototype, {
constructor: {enumerable:false, value:fn}
});
const result = fn.apply(context, args);
return typeof result === 'object' || typeof result === 'function' ? result : context;
}
再来看看 extends
例子
class Child extends Parent {
constructor(name, age) {
super(name, age)
this.name = name
this.age = age
}
getName () {
return this.name
}
}
class Parent {
constructor(name, age) {
this.name = name
this.age = age
}
getName () {
return this.name
}
getAge () {
return this.age
}
}
extends 的作用,
- 将子类的原型对象(prototype)里的__proto__指向超类的原型对象
- 将子类的__proto__指向超类;
- 生成子类构造函数,执行超类的构造函数,执行结果如果是object 或 function,可以作为子类的 this
上面我们分开实现
先实现继承
- 将子类的原型对象(prototype)里的__proto__指向超类的原型对象
- 将子类的__proto__指向超类;
// subClass 子类
// superClass 超类
var _inherits = function(subClass, superClass) {
subClass.prototype = Object.create(superClass,prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true,
enumerable: false
}
})
Object.getPrototypeOf(subClass, superClass)
}
寄生组合式继承
再来完善一下,加点健壮性
- 判断subClass
- 判断superClass
- setPrototypeOf是否存在
var _inherits = function (subClass, superClass) {
// 判断 sub 是否是 function
if (typeof subClass !== 'function') throw new TypeError('subClass must be a function')
// 判断 superClass 是否是 function 或者是 null()
if (typeof superClass !== 'function' && superClass !== null) throw new TypeError('superClass must either be a function or null')
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
enumerable: false,
configurable: true
}
})
if (superClass)
Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : (subClass.__proto__ = superClass)
}
再实现生成子类构造函数
- 生成子类构造函数,执行超类的构造函数,执行结果作为子类的 this;
跟生成class写的一样
var Child = (function(_Parent) {
var Child = function (name, age) {
_checkNew(this, Child)
var _this = _Parent.call(this, name, age) || this;
_createClass(Child, [
{key:'name',value:name,enumerable:true},
{key:'age',value:age,enumerable:true}
], [
{key:'getName',value: function() {return this.name}}
])
return _this;
}
return Child;
})(_Parent)
完善一下
- 调用
_Parent构造函数时,对返回值进行判断,返回值如果不是 object 或 function, 则返回 this;
var Child = (function(_Parent) {
var _possibleConstructorReturn = function(self, call) {
if (!self) throw new Error()
return call && (typeof call === 'function' || typeof call === 'object') ? call : self;
}
var Child = function(name, age) {
_checkNew(this, Child);
var _this = _possbleConstructorReturn(this, Object.getPrototypeOf(Child).call(this, name, age))
_createClass.call(this, Child, [
{key:'name',value:name,enumerable:true},
{key:'age',value:age,enumerable:true}
], [
{key:'getName',value: function() {return this.name}}
])
return _this;
}
})(_Parent)
_inherits 和 生成构造函数结合
var Child = (function(_Parent) {
_inherits(Child, _Parent);
function Child(name, age) {
_checkNew(this, Child);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Child).call(this,name,age))
_createClass.call(this, Child, [
{key:'name',value:name,enumerable:true},
{key:'age',value:age,enumerable:true}
], [
{key:'getName',value: function() {return this.name}}
])
return _this;
}
return Child;
})(_Parent)
super
super的情况很复杂,在不同的地方调用,会是不同的值。
我们先看看《阮一峰老师 ECMAScript 6 入门 》关于 super 的讲解
- 第一种情况,super作为函数调用时,代表父类的构造函数。(这个没啥问题,就是上面的
_prossibleConstrurctorReturn) - 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
来看个书上的案例,和输出值
由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}}
let b = new B();
书里的意思是,如果是 super.x 赋值为 3,这时等于 this.x 赋值为 3; 如果是读取 super.x 的值时,读的是 A.prototype.x,所以返回undefined; 这里很奇怪, 赋值时,super 是 this 读取时,super 是 A.prototype
super属性的 读写是不对称的
具体原因,看了很多别人的文章,基本上是复制粘贴。
有兴趣的,可以看一下
ES6 使用 super 访问父类属性,在 Chrome、Babel、TypeScript 不一致? - 知乎 (zhihu.com)
总结一下
- class
- 只能 new
- 字段
- 公有:public,属性在实例,方法在原型对象
- 静态:static,在类本身
- 私有:private,#,私有作用域,类自己用
- 保护: protected
- extends
- 继承
- 寄生组合式函数
- 子类的prototype =>proto => 父类的prototype
- 子类 => proto => 父类
- 创造构造函数
- super(),调用父类的构造函数,如果返回值是object或function,就替换当前this
- 返回this
- 继承
- new
- 生成空对象
- 空对象的prototype指向构造函数
- 将构造函数在空对象的上下文中执行
- 如果执行结果是object或function,否则返回空对象
- super
- 函数:执行父类的构造函数,看执行结果是否覆盖 this
- 对象:
- 普通方法
- 赋值:super.x = 3 => this.x = 3
- 取值:super.x => 父类.prototype.x
- 静态方法
- 指父类
- 普通方法