[JS] ECMAScript 6 - Inheritance : compare with c#
这一章,估计是js最操蛋的一部分内容。
现代方法:
远古方法:
* 《Javascript面向对象编程(一):封装》【可略,已看】
* 《Javascript面向对象编程(二):构造函数的继承》
* 《Javascript面向对象编程(三):非构造函数的继承》
热身一
调用Object.create(..) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象。
既然是新对象,也就没有必要让o1的属性继承Base里的this.a。

热身二
var animal = function() {};
var dog = function() {}; // 本质是 new Function(),
animal.price = 2000; // 原型预备役,先赋值
dog.prototype = animal; // want to share properties in animal
console.log(dog.price) // undefined, 其实没有“继承”到
/**
* 原型链是依赖于__proto__,而不是prototype!
* 所以,dog.prototype赋值,有屁用!
*/
原理:
dog's __proto__ ----> dog的构造函数的原型,也就是Function的原型
dog.__proto__ == Function.prototype
Function.prototype没有price, 当然dog.price就没有继承到东西
var tidy = new dog();
console.log(tidy.price) // 2000, 反而“继承”到了
Jeff: tidy.__proto__ == dog.prototype == animal
可见,通过_proto__,让tidy与dog有关;但dog与animal之间却没有建立起这层关系。
也就是说:dog不能继承,但dog的实例反而能继承animal的属性。
对象之间的"继承"的五种方法
父类 - 构造函数形式
function Animal(){
this.species = "动物";
}
一、 构造函数绑定
Ref: [JS] Topic - hijack this by "apply" and "call"
function Cat(name,color){
Animal.apply(this, arguments); // 如果是当前对象中没有的属性,就改变this,在"父类"中找
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
二、 prototype模式
Cat.prototype = new Animal(); // 同理“热身二” /**
* Cat.prototype.constructor 指向变化: Cat --> Animal
*/
Cat.prototype.constructor = Cat; // 有必要么?但是,第二行又是什么意思呢?
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
极为重要的一点:prototype改变后,需要再调整回来,为了安全,避免继承链的紊乱。
原型对象的constructor属性,原本就是指向正确的,
现在Cat.prototype都变为了new的新对象,那么也要至少保证Cat.prototype.constructor仍然是指向Cat的。
这个潜规则。
三、 直接继承prototype
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。
缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
function Animal(){ }
Animal.prototype.species = "动物";
Cat.prototype = Animal.prototype;
...
...
四、 利用空对象作为中介
由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。
var F = function(){}; // F是空对象,所以几乎不占内存
F.prototype = Animal.prototype; // 这里的技巧: 利用空对象 做了过渡
Cat.prototype = new F(); // 不会影响到Animal的prototype对象
Cat.prototype.constructor = Cat;
封装成一个函数,便于使用。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层")这等于在子对象上打开一条通道,可以直接调用父对象的方法。
这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
使用方式:
extend(Cat,Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
五、 拷贝继承
function Animal(){}
Animal.prototype.species = "动物";
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
父类 - 普通对象形式
var Chinese = {
nation:'中国'
};
两个对象都是普通对象,不是构造函数,无法使用构造函数方法实现"继承"。
可以使用:非构造函数"的继承。
一、object()方法
function object(o) {
function F() {}
F.prototype = o; // 把子对象的prototype属性,指向父对象,从而使得子对象与父对象连在一起。
return new F();
}
1. 第一步先在父对象的基础上,生成子对象
var Doctor = object(Chinese); // 这里没有在外部显式的new,跟构造函数的继承有点用法的区别
2. 然后,再加上子对象本身的属性
Doctor.career = '医生';
3. 子对象已经继承了父对象的属性
alert(Doctor.nation); //中国
二、浅拷贝
这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
详见原链接:Javascript面向对象编程(三):非构造函数的继承
function extendCopy(p) {
var c = {};
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}
var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); // 中国
三、深拷贝
目前,jQuery库使用的就是这种继承方法。
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
使用方式:
var Doctor = deepCopy(Chinese); // 现在,给父对象加一个属性,值为数组。然后,在子对象上修改这个属性:
Chinese.birthPlaces = ['北京','上海','香港'];
Doctor.birthPlaces.push('厦门'); // 这时,父对象就不会受到影响了。
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港
让我们来瞧瞧,有了Class类之后会如何?
1. 子类需要得到this对象
2. 子类必须在constructor方法中调用super方法
3. 只有调用super之后,才可以使用this关键字,否则会报错
4. 父类的静态方法,也会被子类继承
Parent类:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
Child类:
class ColorPoint extends Point {
constructor(x, y, color) { // 会被默认添加
super(x, y); // 调用父类的constructor(x, y), 这里的x,y是父类的,所以要super中作为了参数
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
Object.getPrototypeOf方法
可以用来从子类上获取父类
Object.getPrototypeOf(ColorPoint) === Point
// true
super 关键字
注意,
super虽然代表了父类A的构造函数,但是返回的是子类B的实例。
super指向父类的原型对象。
即super内部的this指的是B,因此super()在这里相当于:
A.prototype.constructor.call(this)。
- super不是指向父类!
例一:super.p无法调用到this.p = 2

例二:只能调用父类的prototype上

例三:this的不变性【子类普通方法中通过super调用父类的方法时,虽然是父类方法内部的this,却其实是指向当前的子类实例】

例四:超级变态,带我娓娓道来!
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3; // 其实是把上面的x变了,因为在这种情况下,super.x == this.x
console.log(super.x); // undefined
console.log(this.x); //
}
}
let b = new B();
附加题:对于super.x的设计已经无语。

- super指向父类!
例五:用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
-------------------------------------
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
类的 prototype 属性和__proto__属性
Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。
(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
这算是子类沿着线索找寻父类的一个办法。
class A {
}
class B extends A {
}
B.__proto__ === A // true (1)构造函数的继承
B.prototype.__proto__ === A.prototype // true (2)方法的继承
貌似是讲述本质,实现原理。但哥不是很关心。
class A {
}
class B {
}
// B 的实例 继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype); // ---->
// B 继承 A 的静态属性
Object.setPrototypeOf(B, A);
const b = new B();
解释:
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
这两条继承链,可以这样理解:【记住这个就可以了】
(1) 作为一个对象,子类(B)的原型(__proto__属性)是父类(A);
(2) 作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例。
extends 的继承目标
先复习一下:

第一种特殊情况,子类继承Object类。

第二种特殊情况,不存在任何继承。

第三种特殊情况,子类继承null。

原生构造函数的继承
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length //
arr.length = 0;
arr[0] // undefined
ECMAScript 的原生构造函数大致有下面这些。
- Boolean()
- Number()
- String()
- Array()
- Date()
- Function()
- RegExp()
- Error()
- Object()
自定义Error子类的例子,可以用来定制报错时的行为。
class ExtendableError extends Error {
constructor(message) {
super();
this.message = message;
this.stack = (new Error()).stack;
this.name = this.constructor.name;
}
}
class MyError extends ExtendableError {
constructor(m) {
super(m);
}
}
var myerror = new MyError('ll');
myerror.message // "ll"
myerror instanceof Error // true
myerror.name // "MyError"
myerror.stack
// Error
// at MyError.ExtendableError
// ...
Mixin 模式的实现
Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。
const a = {
a: 'a'
};
const b = {
b: 'b'
};
const c = {...a, ...b}; // {a: 'a', b: 'b'}
下面是一个更完备的实现,将多个类的接口“混入”(mix in)另一个类。
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷贝实例属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== "constructor"
&& key !== "prototype"
&& key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
有点难,没看懂。
(完)
[JS] ECMAScript 6 - Inheritance : compare with c#的更多相关文章
- [JS] ECMAScript 6 - Variable : compare with c#
前言 范围包括:ECMAScript 新功能以及对象. 当前的主要目的就是,JS的学习 --> ECMAScript 6 入门 let 命令 js 因为let, i的范围限制在了循环中. var ...
- [JS] ECMAScript 6 - Class : compare with c#
Ref: Class 的基本语法 Ref: Class 的基本继承 许多面向对象的语言都有修饰器(Decorator)函数,用来修改类的行为.目前,有一个提案将这项功能,引入了 ECMAScript. ...
- [JS] ECMAScript 6 - Prototype : compare with c#
开胃菜 prototype 对象 JavaScript 语言的继承则是通过“原型对象”(prototype). function Cat(name, color) { // <----构造函数 ...
- [JS] ECMAScript 6 - Async : compare with c#
一段引言: Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大. 它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对 ...
- [JS] ECMAScript 6 - Array : compare with c#
扩展运算符(spread) 先复习下 rest 参数. (1) argument模式,但不够好. // https://blog.csdn.net/weixin_39723544/article/de ...
- [JS] ECMAScript 6 - Object : compare with c#
Ref: 对象的扩展 Outline: 属性的简洁表示法 属性名表达式 方法的 name 属性 Object.is() Object.assign() 属性的可枚举性和遍历 Object.getOwn ...
- [JS] ECMAScript 6 - String, Number, Function : compare with c#
字符串的扩展 正则的扩展 数值的扩展 函数的扩展 字符串的扩展 js 字符的 Unicode 表示法 codePointAt() String.fromCodePoint() 字符串的遍历器接口 at ...
- [JS] ECMAScript 6 - Set & Map : compare with c#
Ref: Set 和 Map 数据结构 Day 0 - 1所学
- [Node.js] ECMAScript 6中的生成器及koa小析
原文地址:http://www.moye.me/2014/11/10/ecmascript-6-generator/ 引子 老听人说 koa大法好,这两天我也赶了把时髦:用 n 安上了node 0.1 ...
随机推荐
- 以为是tomcat出现using问题,怎么改都改不好终于找到原因
我也是醉了被自己打败了,以上问题困扰我半天是时间,百度好久都没有解决.应该打开tomcat的bin下的starup.bat结果打开了tomcat-src中的了,怪不得死活出现不了startup
- Vs2017 控制台 中文输出是乱码的问题解决
下午直接用vs写的控制台的东西,然后发现控制台输出的中文是乱码,于是就百度了下.同样的是,百度上很多的答案.我就说下我解决的过程.先上图 第一种方案:有可能是控制台的问题.若是控制台的问题,则与VS无 ...
- Windows XP Ghost系统安装
一.双击Ghost系统安装工具,进入Ghost界面 二.依次单击[Local]-[Partition]-[From Image],可以简单记作1-2-3. 弹出对话框,选择.GHO文件,比如XP.GH ...
- 奇怪吸引子---LorenaMod1
奇怪吸引子是混沌学的重要组成理论,用于演化过程的终极状态,具有如下特征:终极性.稳定性.吸引性.吸引子是一个数学概念,描写运动的收敛类型.它是指这样的一个集合,当时间趋于无穷大时,在任何一个有界集上出 ...
- windows性能监控
see also:http://www.cnblogs.com/upDOoGIS/archive/2010/11/19/1881970.html CPU Processor : % Processor ...
- CentOS 7搭建Linux GPU服务器
1. CUDA Toolkit的安装 到https://developer.nvidia.com/cuda-gpus查询GPU支持的CUDA版本: 到https://developer.nvidia. ...
- [转]抛弃jQuery,使用原生JavaScript
原文链接 Document Ready 事件 在jQuery中,document.ready可以让代码在整个文档加载完毕之后执行: $(document).ready(function() { // ...
- 基于Centos体验自然语言处理 by PHP SDK
系统要求:CentOS 7.2 64 位操作系统 准备工作 获取 SecretId 和 SecretKey1 前往 密钥管理 页面获取你的 SecretId 和 SecretKey 信息,这些信息将会 ...
- cocos2d-x与UIKit混合编程实现半透明效果
关键词 cocos2d-x, UIKit, transparent 问题 cocos2d-x使用一个专门的OpenGL View进行渲染, 它的渲染和UIKit是分开进行的, 因此我们使用时一般是把c ...
- Activity标题(title)的显示和隐藏
开发Android应用程序,我们总会遇到Activity的title把显示的内容遮挡了一部分.如果能把它去掉,我们的应用界面就会变得更加简洁,那该多好.下面有两种方法可以去掉: (方法一):通过一句J ...