面向对象

2018-11-28  本文已影响0人  陈裔松的技术博客

写在前面:阅读本文之前,请先阅读上一篇文章原型链

对象的创建

第一种方法:使用字面量,或者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}}

对象的存储

对象的存储.png

关键字

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背后也是这个原理

上一篇 下一篇

猜你喜欢

热点阅读