深入理解javascript原型和原型链

构造函数

通过 new 函数名 来实例化对象的函数叫构造函数。
任何的函数都可以作为构造函数存在。之所以有构造函数与普通函数之分,主要从功能上进行区别的,构造函数的主要 功能为 初始化对象,特点是和new 一起使用。new就是在创建对象,从无到有,构造函数就是在为初始化的对象添加属性和方法。构造函数定义时首字母大写(规范)。

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

let p1 = new Person('张三'); // 实例化

console.log(p1); // Person {name: "张三"}

此时,p1就是一个新对象。

1. new一个新对象的过程,发生了什么?

  1. 创建一个空对象obj {}
  2. 空对象的_proto_指向了构造函数的prototype成员对象
  3. 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
  4. 如果构造函数中没有返回其它对象,那么返回 this,即创建的这个的新对象,否则,返回构造函数中返回的对象

对new理解:new 申请内存, 创建对象,当调用new时,后台会隐式执行new Object()创建对象。所以,通过new创建的字符串、数字是引用类型,而是非值类型。

2. 手写new函数

function _new(func, ...args) {
    // 1. 创建空对象
    let obj = {};
    // 2. 空对象的_proto_指向了构造函数的prototype成员对象
    obj.__proto__ = func.prototype; // 一二步合并就相当于 let obj = Object.create(func.prototype)
    // 3. 使用apply调用构造器函数,属性和方法被添加到 this 引用的对象中
    let result = func.apply(obj, args);
    // 4. 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj;
}

测试用例:

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

let obj = _new(Person, 'xia', 20);

console.log(obj); // Person {name: "xia", age: 20}

3. 构造函数上的方法

  1. 在构造函数上直接定义方法(不共享)
function Person() {
    this.say = function () { // 直接定义方法
        console.log('hello');
    }
}

let p1 = new Person();
let p2 = new Person();
p1.say(); // hello
p2.say(); // hello

console.log(p1.say === p2.say); // false

很明显,p1 和 p2 指向的不是一个地方。 所以 在构造函数上通过 this 来添加方法的方式来生成实例,每次生成实例,都是新开辟一个内存空间存方法。这样会导致内存的极大浪费,从而影响性能

  1. 通过原型添加方法(共享)

构造函数通过原型分配的函数,是所有对象共享的。

function Person(name) {
    this.name = name;
}
Person.prototype.say = function () { // 通过原型添加方法
    console.log('hello ' + this.name);
}

let p1 = new Person('张三');
let p2 = new Person('李四');
p1.say(); // hello 张三
p2.say(); // hello 李四

console.log(p1.say === p2.say); // true

所以我们经常 将公共属性定义到构造函数里,将公共方法放到原型对象上

点击查看“构造函数的五种继承方式


原型

1. 什么是原型?

上面的Person.prototype就是原型(也叫显式原型),它是一个对象,我们也称它为原型对象。

2. 原型的作用是什么?

原型的作用,就是共享方法。
我们通过 Person.prototype.say 可以共享方法,不会反复开辟存储空间,减少内存浪费。

3. 原型中this的指向是什么?

指向实例化对象p1、p2


函数对象

  1. __proto__:任何对象( JS 中万物皆对象)都有proto属性(隐式原型)
  2. prototype:所有函数(仅限函数)都拥有 prototype 属性(显式原型)
  3. constructor:所有的 prototype 和 实例化对象 都有一个constructor 属性,都指向关联的构造函数本身

当我们声明一个function关键字的方法时,会为这个方法添加一个prototype属性,指向默认的原型对象,并且此prototype的constructor属性也指向方法对象。此二个属性会在创建对象时被对象的属性引用。

function Hello() {}; // 构造函数
var h = new Hello();  // 实例化对象


// 所有函数都有个prototype属性(显式原型)
console.log(Hello.prototype); // Object {}  原型对象
// 构造函数的prototype属性有个constructor属性,指向构造函数本身
console.log(Hello.prototype.constructor === Hello); // true
// 实例化对象没有prototype属性、只有函数才有prototype属性
console.log(h.prototype); // undefined


// 实例化对象的constructor属性指向构造函数本身
console.log(h.constructor === Hello); // true
// 即
console.log(h.constructor === Hello.prototype.constructor); // true 


// 所有引用类型都拥有__proto__属性(隐式原型)
console.log(h.__proto__ === Hello.prototype); // true  
// 即
console.log(h.__proto__ === h.constructor.prototype); //true
// 即
console.log(Hello.prototype === h.constructor.prototype); //true
// 即
console.log(Hello === h.constructor); // true


1. prototype

所有函数(仅限函数)都拥有 prototype 属性(显式原型)

function Person() {};

Person.prototype.sayHello = function() {
    console.log('Hello!')
}

var person1 = new Person();
var person2 = new Person();

console.log(person1.sayHello === person2.sayHello) // true,同一个方法

prototype对象用于放某同一类型实例的共享属性和方法,实质上是为了内存着想。

讲到这里,你需要知道的是,所有函数本身是Function函数的实例对象,所以Function函数中同样会有一个prototype对象放它自己实例对象的共享属性和方法。

// 实例化对象的constructor属性 指向构造函数本身
console.log(person1.constructor === Person); // true
console.log(person2.constructor === Person); // true

// Person是Function的实例对象
console.log(Person.constructor === Function); // true
console.log(Function.constructor === Function); // true

如下图:



2. _proto _

任何对象(JS中万物皆对象)都有proto属性(隐式原型)

function Person() {};

Person.prototype.sayHello = function() {
    console.log('Hello!')
}

var person1 = new Person();
var person2 = new Person();

// 所有引用类型都有__proto__属性,指向构造函数的显示原型
console.log(person1.__proto__ === Person.prototype); // true  
console.log(person2.__proto__ === Person.prototype); // true  
/*1、字面量方式*/
var a1 = {};
console.log(a1.constructor === Object); // true (即构造器Object)
console.log(a1.__proto__ === a1.constructor.prototype); // true
console.log(a1.__proto__ === Object.prototype); // true


/*2、构造器方式*/
var A = function (){}; 
var a2 = new A();
console.log(a2.constructor === A); // true(即构造器function A)
console.log(a2.__proto__ === a2.constructor.prototype); // true
console.log(a2.__proto__ === A.prototype); // true


/*3、Object.create()方式*/
var a1 = {a:1} 
var a2 = Object.create(a1);
console.log(a2.constructor === Object); // true  (即构造器Object)
console.log(a2.__proto__ === a1); // true 
console.log(a2.__proto__ === a2.constructor.prototype); //false

3. constructor

所有的 prototype 和 实例化对象 都有一个constructor 属性,都指向关联的构造函数本身

function Person() {};
var person1 = new Person();
var person2 = new Person();

console.log(person1.constructor === Person); // true
console.log(Person.constructor === Function); // true
console.log(Function.constructor === Function); // true
  1. person1 与 person2 是 Person 对象的实例,他们的 constructor 指向创建它们的构造函数,即 Person 函数;
  2. Person 是函数,但同时也是 Function 实例对象,它的 constructor 指向创建它的构造函数,即 Function 函数;
  3. Function 函数,它是JS的内置对象,它的构造函数是它自身,所以内部 constructor 属性指向自己。

所以constructor属性其实就是一个拿来保存自己构造函数引用的属性,没有其他特殊的地方。


原型链

var A = function () {};
var a = new A();

// 由__proto__组成的原型链
console.log(a.__proto__ === A.prototype); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true   null表示“没有对象”,即该处不应该有值。

下图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function () {
    console.log('hello', this.name);
};

let student = new Person('张三', 18);

console.log(student.__proto__ === Person.prototype); // true
console.log(student.__proto__.say === Person.prototype.say); // true
console.log(student.__proto__.say === student.say); // true
console.log(student.say === Person.prototype.say); // true

对象之所以可以使用构造函数prototype原型对象的属性和方法,就是因为对象有proto原型的存在

function Parent(month){
    this.month = month;
}

var child = new Parent('Ann');

console.log(child.month); // Ann
console.log(child.father); // undefined

child中查找某个属性时,会执行下面步骤:


访问链路为:


终极图

这个图要是看懂了,原型与原型链就基本摸清了。


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

推荐阅读更多精彩内容