JavaScript 面向对象的那些事儿

一、类与实例

1、类的声明
// 使用构造函数来作类的声明
var Me = function () {
    this.name = 'pengxiaohua';
};

// es6中class的声明
class Me2 {
    constructor () {
        this.name = 'xiaohuapeng';
    }
}
2、生成实例

生成实例,都是用new方法,如下:

// 如果没有参数,`new Me()`中的括号是可以省掉的
new Me();  // Me  {name: 'pengxiaohua'};
new Me2();  // Me2  {name: 'xiaohuapeng'};

二、类与继承

JavaScript的继承的基本原理还是对原型链的操作。

1、继承的几种方式
  • ① 借助构造函数实现继承
function Father () {
    this.name = 'father';
}

function Child () {
    Father.call(this);  // 将Father的this指向Child的this,此处用apply也可以
    this.type = 'child';
}

console.log(new Child());  // Child {name: "father", type: "child"}

缺点:子类只能继承父类构造函数里的方法,不能继承父类原型对象上的方法,如:

Father.prototype.say = 'say Hi';

new Child()实例中是没法继承say方法的。

  • ② 借助原型链实现继承
function Father2 () {
    this.name = 'father2';
}

function Child2 () {
    this.type = 'child';
}

Child2.prototype = new Father2();   // 关键

console.log(new Child2().__proto__);  // Father2 {name: "father2"}

根据原型链知识可以知道,Child2 构造函数的实例new Child2()的__proto__属性和 Child2 的原型prototype相等,即new Child2().__proto__ === Child2.prototype,因为Child2.prototype = new Father2();,Child2将其原型赋值给父类Father2的实例new Father2(),所以new Child2().__proto__与new Father2()相等。则new Child2()可以拿到父类 Father2 中的方法,继而实现了继承。

缺点: 因为Child2 的实例对象都引用的同一个对象,即父类Father2的实例对象new Father2(),当Child2 生成多个实例对象的时候,其中一个实例对象改变,其他实例对象都会改变,如下例子:

function Father2 () {
    this.name = 'father2';
    this.arr = [1,2,3];
}

function Child2 () {
    this.type = 'child2';
}

Child2.prototype = new Father2();   // 关键
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.arr);
console.log(s2.arr);
// [1,2,3]
// [1,2,3]

当修改其中一个实例化对象时,另一个也会跟着改变,如下:

// 给arr添加一个数
s1.arr.push(4);
console.log(s1.arr);
console.log(s2.arr);
// [1,2,3,4]
// [1,2,3,4]
  • ③ 组合继承
function Father3 () {
    this.name = 'father3';
    this.arr = [1,2,3];
}

function Child3 () {
    Father3.call(this);
    this.type = 'child3';
}

Child3.prototype = new Father3();
var s3 = new Child3();
var s4 = new Child3();
s3.arr.push(4);
console.log(s3.arr);
console.log(s4.arr);
// [1,2,3,4]
// [1,2,3]

组合继承方式,弥补了上面2中方式的缺点。
缺点: Father3这个父级构造函数执行了2次,一次是子类Child3实例化new Child3()的时候,Father3.call(this)调用了一次Father3这个父级构造函数,还有一次是Child3.prototype = new Father3();,对Father3()进行实例化的时候。
这2次是没有必要的,会多消耗了一点内存。

  • ④ 组合继承优化方案1
function Father4 () {
    this.name = 'father4';
    this.arr = [1,2,3];
}

function Child4 () {
    Father4.call(this);    // 拿到父类的构造体里的属性和方法
    this.type = 'child4';
}

Child4.prototype = Father4.prototype; // 针对第一种方法缺点,直接继承父类原型对象就行了
var s5 = new Child4();
var s6 = new Child4();
s5.arr.push(4);
console.log(s5.arr);
console.log(s6.arr);
// [1,2,3,4]
// [1,2,3]

这种方式通过call方法拿到父类构造体里的属性和方法,同时通过对prototype赋值,直接继承父类原型对象上的方法和属性,避免了重复。是一种比较完美的继承方法。
但还是有一个小缺点的:

s5 instanceof Child4;    // true
s5 instanceof Father4;   // true
s5.constructor;
/*
Father4() {
    this.name = 'father4';
    this.arr = [1,2,3];
*/
}

当用instanceof来判断s5是不是Child4和Father4的实例的时候,都显示true,表明s5都是他们2个的实例。
因为instanceof有时是不准确的,当用constructor来判断的时候,发现s5是父类Father4的实例化,子类Child4没有constructor,它的constructor是从父类的constructor中继承的,这也造成了s5虽然是子类Child4的实例,但用instanceof检测时却既是Child4的也是Father4的实例对象。无法判断s5这个实例是父类创造的还是子类创造的。

  • ⑤ 组合继承优化方案2 (寄生组合式继承)
function Father5 () {
    this.name = 'father5';
    this.arr = [1,2,3];
}

function Child5 () {
    Father4.call(this);
    this.type = 'child5';
}

Child4.prototype = Object.create(Father5.prototype);
var s7 = new Child5();
var s8 = new Child5();
s7.arr.push(4);
console.log(s7.arr);    // [1,2,3,4]
console.log(s8.arr);    // [1,2,3]
s5 instanceof Child4;    // true
s5 instanceof Father4;   // false
s5.constructor;
/*
function Child5 () {
    Father4.call(this);
    this.type = 'child5';
}
*/

在上一种方法中,Child4.prototype = Father4.prototype;,子类Child4和父类Father4的构造函数指向的是同一个,所以无法区分实例s5是通过父类还是通过子类来创造的。
通过Object.create()就解决了这个问题,这种方法通过、创建一个中间对象,把父类和子类两个原型对象区分开,达到了父类和子类原型对象的一个隔离。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • 目录 导语 1.理解对象和面向对象的程序设计 2.创建对象的方式 3.JavaScript的继承机制 3.1 原型...
    犯迷糊的小羊阅读 808评论 0 4
  • class的基本用法 概述 JavaScript语言的传统方法是通过构造函数,定义并生成新对象。下面是一个例子: ...
    呼呼哥阅读 4,091评论 3 11
  • 一、理解对象 1.创建 ①构造函数 new Object ②对象字面量 var o = {}; 2.属性类型 ①数...
    duJing阅读 418评论 0 0
  • 记得上中学时候,曾学过一篇课文是著名作家魏巍写的《谁是最可爱的人》,学完后我才知道是我们人民子弟兵。 现在,...
    df872c2ae1f1阅读 646评论 0 7
  • 01 刚下飞机就见苏哩直奔而来,给我一个大大的拥抱。她眼眶微红,不知道是激动,还是久别重逢。但我却推开了她,眼里满...
    420_c644阅读 331评论 0 0