JavaScript深入理解 —— 原型、原型链和继承

普通对象和函数对象

函数对象:使用函数声明函数表达式Function构造函数创建的对象

函数实际上是对象,每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。

普通对象:除了函数对象以外的对象,都是普通对象。

示例:

// 定义函数的三个方法
function f1() {};       // 函数声明
var f2 = function() {};         // 函数表达式
var f3 = new Function("num1", "num2", "return num1 + num2");        // Function构造函数

// 创建对象
var o1 = {};        // 对象字面量
var o2 = new Object();      // Object构造函数
var o3 = new f1();      // f1构造函数

// 检测类型
console.log(typeof Function);  //function
console.log(typeof Object);  //function

console.log(typeof f1);  //function
console.log(typeof f2);  //function
console.log(typeof f3);  //function

console.log(typeof o1);  //object
console.log(typeof o2);  //object
console.log(typeof o3);  //object

构造函数

构造函数: 通过new关键字方式调用的函数。

在构造函数内部(也就是被调用的函数内):

  1. this 指向实例对象 Object
  2. 这个实例对象的 __proto__ 属性指向构造函数的 prototype
  3. 这个实例对象的constructor属性指向构造函数
  4. 如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 —— 也就是实例对象。

示例:

function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}
var person1 = new Person("Hysunny");
var person2 = new Person("Max");

console.log(person1 instanceof Person);     // true
console.log(person1.constructor === person2.constructor);       // true

// 实例的__proto__属性指向构造函数的prototype
console.log(person1.__proto__ === Person.prototype);        // true

// 实例的constructor指向构造函数
console.log(person1.constructor === Person);        // true

原型对象

在 JavaScript 中,每当定义一个对象(函数)时候,对象中都会包含一些预定义的属性。其中就包含prototype属性,这个属性指向函数的原型对象

  1. 原型对象是一个普通对象(除Function.prototype之外),存储所有实例对象共享的方法和属性;
  2. 构造函数原型对象是构造函数的一个实例。
  3. 原型对象的constructor属性指向prototype属性所在的函数;
  4. 每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性,这两个属性指向函数的原型对象

示例:

function Person(name) {
  this.name = name;
}

Person.prototype. sayName = function() {
  console.log(this.name);
}

var person1 = new Person("Hysunny");
var person2 = new Person("Max");

// 构造函数、原型对象和实例之间有这样的联系
Person.prototype.constructor === Person;
person1.__proto__ === Person.prototype;
person1.constructor === Person;

console.log(person1.constructor === Person);        // true
console.log(Person.prototype.constructor === Person);       // true
console.log(Person.prototype.constructor === person1.constructor);      // true
// => 原型对象是构造函数的一个实例

console.log(Person.prototype.constructor == Person);        // true
// => 原型对象的constructor属性指向prototype属性所在的函数

console.log(person1.__proto__ === Person.prototype);        // true
// => __proto__属性指向函数的原型对象

注: Function.prototype虽为函数对象,但是是个空函数,没有prototype属性。

console.log(typeof Function.prototype) // function
console.log(typeof Function.prototype.prototype) // undefined

原型链

从上面的分析我们可以知道:JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个__proto__内置属性,用于指向创建它的函数对象的原型对象prototype

以上面的例子为例:

  1. person1具有__proto__属性,指向Person.prototype
  2. Person.prototype具有__proto__属性,指向Object.prototype
  3. Object.prototype具有__proto__属性,指向null
console.log(person1.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null

这些__proto__串起来,就构成了原型链,原型链的顶端为null。绘出原型链图如下:

prototype-chain.png

简化如下:

prototype.png

疑点解释:

1. Object.__proto__ === Function.prototype  // true
// Object是函数对象,是通过new Function()创建,所以Object.__proto__指向Function.prototype。

2. Function.__proto__ === Function.prototype        // true
// Function 也是对象函数,也是通过new Function()创建,所以Function.__proto__指向Function.prototype。

3. Function.prototype.__proto__ === Object.prototype // true
// Function本身也是一个构造函数,所以`Function.prototype.__proto__`指向`Object.prototype`

继承

继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承实现继承 。接口继承只继承方法签名,而实现继承则继承实际的方法.由于js中方法没有签名,在ECMAScript中无法实现接口继承.ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的.

为什么要实现继承呢?

最重要的原因之一就是为了抽象(复用代码)

将公共的代码封装成一个基类,其他子类继承基类,并发展自己特有的属性和样式。

关于实现继承的方式,我们将在下一篇文章中进行讨论。


参考资料:

《JavaScript 高级程序设计》 第三版

js原型与原型链终极详解

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

推荐阅读更多精彩内容

  • 理解 javascript 的原型链及继承 以上所有的运行结果都是 true; 三种构造对象的方法: 通过对象字面...
    你期待的花开阅读 1,510评论 0 3
  • 原型链是一种机制,指的是 JavaScript 每个对象都有一个内置的 __proto__ 属性指向创建它的构造函...
    劼哥stone阅读 4,413评论 15 80
  • 前言 原型,作为前端开发者,或多或少都有听说。你可能一直想了解它,但是由于各种原因还没有了解,现在就跟随我来一起探...
    无亦情阅读 667评论 0 8
  • 在JS中,原型链是一个重要的概念,不管是继承还是属性值的查找中,都用到了原型链的基本知识,有些朋友经常问我一些关于...
    彬_仔阅读 1,589评论 2 20
  • (宵夜)三更夜悄静,入夜了无声。追追连续剧,看看战争片。肚饥腹中响,寻块咸饼干。加杯热奶茶,平慰小腹空。
    甘朝武阅读 273评论 0 0