美文网首页
面向对象

面向对象

作者: 陈裔松的技术博客 | 来源:发表于2018-11-28 19:43 被阅读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背后也是这个原理

相关文章

  • PHP全栈学习笔记8

    面向对象的基本概念,面向对象编程,oop,面向对象,面向对象的分析,面向对象的设计,面向对象的编程,什么是类。 类...

  • PHP全栈学习笔记8

    面向对象的基本概念,面向对象编程,oop,面向对象,面向对象的分析,面向对象的设计,面向对象的编程,什么是类。 类...

  • 总结.Net基础知识——献给即将入坑的同行们(一期)

    什么是面向对象 面向对象OO = 面向对象的分析OOA + 面向对象的设计OOD + 面向对象的编程OOP; 通俗...

  • 面向对象基础

    面向对象编程包括: 面向对象的分析(OOA) 面向对象的设计(OOD) 面向对象的编程实现(OOP) 面向对象思想...

  • 20-OOP类与对象

    面向对象 Object Oriented 学习面向对象:XXOO 面向对象的学习: 面向过程和面向对象的区别: 面...

  • JavaScript面向对象核心知识归纳

    面向对象 概念 面向对象就是使用对象。面向对象开发就是使用对象开发。 面向过程就是用过程的方式进行开发。面向对象是...

  • 面向对象(未完成)

    面向对象 标签(空格分隔): 面向对象 第一章:面向对象(上) 什么叫面向对象 面向过程、面向对象、两者区别 构造...

  • 面向对象:创建对象&继承

    博客内容:什么是面向对象为什么要面向对象面向对象编程的特性和原则理解对象属性创建对象继承 什么是面向对象 面向对象...

  • 面向对象

    了解什么是面向对象 首先,我们学习面向对象,要了解什么是面向对象,面向对象的重要概念:类,对象。 面向对象提出的这...

  • 面向对象的三大基本特征和五大设计原则

    1、面向对象概念 1.1、理解面向对象 面向对象是相对面向过程而言; 面向对象和面向过程都是一种思想; 面向过程:...

网友评论

      本文标题:面向对象

      本文链接:https://www.haomeiwen.com/subject/hwgzqqtx.html