写在前面:阅读本文之前,请先阅读上一篇文章原型链
对象的创建
第一种方法:使用字面量,或者new Object创建对象
// 字面量方式创建对象
var object1 = {name:'object1'};
// 使用new Object创建对象,以下两种方式是一样的
var object2 = new Object({name:'object2'});
var object3 = new Object();
object3.name = 'object3';
console.log(object1); // {name: "object1"}
console.log(object2); // {name: "object2"}
第二种方法:使用显式的构造函数创建对象
var M = function(name){
this.name = name
}
var object3 = new M('object3');
console.log(object3); // {name: "object3"}
第三种方法:使用原型对象创建对象
var P = {name:'object4'};
var object4 = Object.create(P);
console.log(object4); // {}
console.log(object4.name); // "name"
对象的深拷贝
// 深拷贝
deepClone = (source) => {
// 判断拷贝的目标是数组还是对象
const targetObj = source.constructor === Array ? [] : {};
for (let key in source) {
if (typeof source[key] === "object") {
// 如果是对象,就执行递归
targetObj[key] = this.deepClone(source[key]);
} else {
// 如果不是对象,直接赋值
targetObj[key] = source[key];
}
}
return targetObj;
}
// Deep Clone
obj1 = { a: 0 , b: { c: 0}};
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
对象的存储

关键字
instanceof
// 用于判断一个实例是否属于某种类型
var arr = new Array();
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true,Array继承自Object
delete
// 只能用于删除属性,不能用于删除方法
function fun(){
this.name = "zhangsan";
this.say = function (){
console.log(this.name);
}
}
var obj = new fun();
obj.say(); // zhangsan
delete obj.say(); // 这样是删除不掉的
obj.say(); // zhangsan
obj.say(); // zhangsan
delete obj.say; // 这样可以删除掉,删除了say属性
obj.say(); // 报错,obj.say is not a function
类的声明
1,用构造函数声明类
function Animal1() {
this.name = 'animal1';
}
2,用ES6中的class声明类
class Animal2 {
constructor(){
this.name = 'animal2';
}
}
类的实例化
const animal1 = new Animal1();
const animal2 = new Animal2();
console.log(animal1); // {name: "animal1"}
console.log(animal2); // {name: "animal2"}
类的继承
1,借助构造函数实现继承
// 有时这种方式也叫对象冒充
function Parent1() {
this.name = 'parent1';
}
function Child1() {
Parent1.call(this); //用apply也可以
this.type = 'child1';
}
const child1 = new Child1;
console.log(child1); // {name: "parent1", type: "child1"}
构造函数Child1中,使用call或者apply都是可以的,call和apply改变的是函数运行的上下文。
这里执行Parent1.call(this),会改变Parent1中的this指向,指向call(this)
里的this
,也就是最终指向Child1的实例化对象。
以此来达到继承父类的效果。
缺陷:这种方式无法继承父类的原型对象中的属性和方法。
Parent1.prototype.age = 30;
console.log(child1.age); // undefined
试验一下,在构造函数Parent1的原型对象上,添加属性age=10;
从显示结果可以看出,原型对象中的属性并没有继承过来。
2,借助原型链实现继承
function Parent2(){
this.name = 'parent2';
this.play = [1,2,3];
}
function Child2(){
this.type = 'child2';
}
Child2.prototype = new Parent2();
const child2 = new Child2();
console.log(child2); // {type: "child2"}
console.log(child2.name); // parent2
console.log(child2.type); // child2
console.log(child2.play); // [1, 2, 3]
在这里,把Parent2的实例对象赋值给了构造函数Child2的原型对象。
根据原型链原理,Parent2(包括Parent2原型链)上的属性/方法,构造函数Child2的实例对象child2都可以访问到,从而实现对父类的继承。
缺陷:这种方式会造成实例对象间的相互影响
const s1 = new Child2();
const s2 = new Child2();
console.log(s1.play); // [1, 2, 3]
console.log(s2.play); // [1, 2, 3]
s1.play.push(4);
console.log(s1.play); // [1, 2, 3, 4]
console.log(s2.play); // [1, 2, 3, 4]
console.log(s1.__proto__ === s2.__proto__); // true
试验一下,再用构造函数Child2实例化对象s1和s2。然后改变s1.play的值,可以看到s2.play的值也随之改变了,这显然不是我们想要的效果。
造成这个现象的原因,是因为s1的原型对象和s2的原型对象是同一个对象。所以当s1.play.push(4)改变原型对象上的值是,s2.play也跟着改变了。
3,组合方式(组合方式1和方式2)实现继承
function Parent3() {
this.name = 'parent3';
this.play = [1,2,3];
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Parent3.prototype.age = 30;
Child3.prototype = new Parent3();
const child3 = new Child3();
console.log(child3); // {name: "parent3", play: Array(3), type: "child3"}
const s3 = new Child3();
const s4 = new Child3();
console.log(s3.age); // 30
console.log(s4.age); // 30
console.log(s3.play); // [1, 2, 3]
console.log(s4.play); // [1, 2, 3]
s3.play.push(4);
console.log(s3.play); // [1, 2, 3, 4]
console.log(s4.play); // [1, 2, 3]
可以看到,这种组合方式很好的规避了方式1和方式2的问题。那这个方式有没有缺陷呢?是有的。构造函数Parent3在Parent3.call(this);
的时候执行了一次,然后在Child3.prototype = new Parent3();
的时候又执行了一次,这样执行两次完全是没有必要的。
缺陷:需要执行两次构造函数Parent3。
优化1:Child3.prototype = new Parent3(); => Child3.prototype = Parent3.prototype;
因为我们只是希望构造函数Parent3的原型对象有改变(比如增加了某个属性/方法)的时候,Child3的实例能够访问到。所以其实不必用构造函数Parent3的实例来赋值,直接用构造函数Parent3的原型对象来赋值就可以了。
function Parent3() {
this.name = 'parent3';
this.play = [1,2,3];
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Parent3.prototype.age = 30;
Child3.prototype = Parent3.prototype;
const child3 = new Child3();
console.log(child3); // {name: "parent3", play: Array(3), type: "child3"}
const s3 = new Child3();
const s4 = new Child3();
console.log(s3.age); // 30
console.log(s4.age); // 30
console.log(s3.play); // [1, 2, 3]
console.log(s4.play); // [1, 2, 3]
s3.play.push(4);
console.log(s3.play); // [1, 2, 3, 4]
console.log(s4.play); // [1, 2, 3]
从输出内容可以看到,最终的结果是一样的,但是构造函数Parent3只执行了一次。那这个优化方式还有没有缺陷呢?其实还是有的,我们来看一下。
console.log(s5 instanceof Child4); // true
console.log(s5 instanceof Parent4); // true
console.log(s5.constructor); // Parent4
从输出内容可以看到,即使用s5.constructor查询s5的构造函数是哪个,结果也还是Parent4。这显然不是我们想要的结果,s5的构造函数应该是Child4才对。而造成这个现象的原因,就是因为Child3.prototype = Parent3.prototype;
这个处理。
缺陷:查找实例的构造函数,结果不够准确
补充一点,这个缺陷不是因为优化之后才出现的,在没优化之前就已经有这个问题,只不过没有提到而已。怎么办?我们来看优化2。
优化2(完美):使用Object.create()创建原型对象
首先,使用Object.create(Parent3.prototype)
创建了一个空对象,这个空对象的原型对象就是Parent3.prototype,也就是构造函数Parent3的原型对象。
然后,通过Child3.prototype = Object.create(Parent3.prototype);
把刚才创建的空对象赋值给了Child3的原型对象。
最后,通过Child3.prototype.constructor = Child3;
设置Child3的原型对象所指向的构造函数,就是Child3。
function Parent3() {
this.name = 'parent3';
this.play = [1,2,3];
}
function Child3() {
Parent3.call(this);
this.type = 'child3';
}
Parent3.prototype.age = 30;
Child3.prototype = Object.create(Parent3.prototype);
Child3.prototype.constructor = Child3;
const child3 = new Child3();
console.log(child3); // {name: "parent3", play: Array(3), type: "child3"}
console.log(child3 instanceof Child3); // true
console.log(child3 instanceof Parent3); // true
console.log(child3.constructor); // Child3
丛输出结果可以看到,没有任何问题,这是一个完美的方案。
以上,就是继承的原理,es6的extend背后也是这个原理
网友评论