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 // true
me.constructor === Pserson // true
Persion.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 也指向了 Cat
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // error
cat1.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 overwritten
me.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); //北京, 上海, 香港