ECMAScript is an object-oriented programming language supporting delegating inheritance based on prototypes.
ECMAScript是一种面向对象语言,支持基于原型的委托式继承。
原型链
在 es5 中 new 后面的是构造函数,对应 constructor
构造函数的属性prototype 是原型
var a = new A(); 做了:// 1. 首先创建一个空对象var o = new Object();// 2. 将空对象的原型赋值为构造器函数的原型o.__proto__ = A.prototype;// 3. 更改构造器函数内部this,将其指向新创建的空对象A.call(o);
最后当然是返回了。返回的时候会进行一个判断,如果构造器函数(这里即A)设置了返回值,并且返回值是一个Object类型的话,就直接返回该Object,否则返回新创建的空对象(这里即o);
// Persion是函数,Persion在new调用的时候变成构造函数function Persion (name) {// name 是构造参数this.name = name;this.showName = function () {console.log(this.name);}/}var me = new Persion('zbj');var other = new Persion('yqy');me.showName();other.showName();
上面构造函数生成的每个实例都有各自的showName方法的副本,
这不仅无法做到数据共享,也是极大的资源浪费。
实现共享方法,加入 prototype
function Persion (name) {this.name = name;}// Persion是函数// Persion.prototype 是函数自定义属性// Persion.prototype 指向的是 原型对象// 可以理解// persion.prototype有constructor和__proto__(指向Object.prototype)两个属性Persion.prototype.showName = function () {console.log(this.name);}// me有name和showName成员变量var me = new Persion('zbj');me.showName();// me.constructor 指向构造函数me.constructor.prototype.showName = function () {console.log('error')}
// 对象有个__proto__指向原型对象me.__proto__ === Persion.prototype // true// 原型对象有个 constructor 指向构造函数me.__proto__.constructor === me.constructor // trueme.constructor === Pserson // truePersion.prototype.constructor === Persion// 构造函数有个 prototype 指向原型对象// 原型对象也有一个__proto__
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。这就是原型对象的特殊之处。顺着链条去找
如果改了prototype,要把prototype的constructor改回来
继承
继承的实现原理
proto
proto 并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的JS引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用 Object.getPrototypeOf 方法来获取实例对象的原型,然后再来为原型添加方法/属性。
构造函数的继承
构造函数绑定
function Animal(){this.species = "动物";}Animal.prototype.eat = function () {console.log("amimal eat")}function Cat(name,color){Animal.apply(this, arguments);this.name = name;this.color = color;}var cat1 = new Cat("大毛","黄色");alert(cat1.species); // 动物cat1.eat() // typeError
Cat 没有继承 prototype
prototype模式
function Cat(name,color){this.name = name;this.color = color;}Cat.prototype = new Animal();Cat.prototype.constructor = Cat;var cat1 = new Cat("大毛","黄色");cat1.eat()// animal eat
缺点是,继承了所有的实例,可能只想继承原型
直接继承prototype
function Cat(name,color){this.name = name;this.color = color;}// Cat 和 Animal指向了同一个原型Cat.prototype = Animal.prototype;// 实际上把 Animal.prototype.constructor 也指向了 CatCat.prototype.constructor = Cat;var cat1 = new Cat("大毛","黄色");alert(cat1.species); // errorcat1.eat() // animal eat
缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
利用空对象作为中介
var F = function(){};F.prototype = Animal.prototype;Cat.prototype = new F();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;}extend(Cat,Animal);var cat1 = new Cat("大毛","黄色");alert(cat1.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); // 动物
对象的继承
- 原型链的方法
Object.create() 参数:proto
新创建对象的原型对象。
propertiesObject
Object.create = function (proto,propertiesObject) {function F() {}F.prototype = proto;return new F();}
const person = {isHuman: false,printIntroduction: function () {console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);}};const me = Object.create(person);me.name = "Matthew"; // "name" is a property set on "me", but not on "person"me.isHuman = true; // inherited properties can be overwrittenme.printIntroduction();// expected output: "My name is Matthew. Am I human? true"
- 浅拷贝
function extendCopy(p) {var c = {};for (var i in p) {c[i] = p[i];}c.uber = p;return c;}
但是,这样的拷贝有一个问题。那就是,如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。
- 深拷贝
所谓”深拷贝”,就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用”浅拷贝”就行了。
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); //北京, 上海, 香港
