JavaScript原型和原型链

910 阅读5分钟

原型和原型链很重要,但是很容易忘,以前懂了现在又忘了,所以总结一下原型链的来龙去脉,帮助大家同时也让自己能够快速的捡起来

一、原因:

1、构造函数的弊端

function Person(name, age) {
    // 在构造函数内部可以通过this来给创建的对象添加属性和方法
    // this.xxx = yyy
    this.name = name
    this.age = age
    this.sayHi = function () {
        console.log(`${name}今天你吃了吗?`)
    }
}
let p1 = new Person('张三',18)
p1.sayHi() // '张三今天你吃了吗?'
let p2 = new Person('李四',19)
p2.sayHi() // '李四今天你吃了吗?'

console.log('p1 == p2的结果为:',p1 == p2); // false 
console.log('p1 == p2的结果为false的原因是:== 两边都是new出来的新对象,内存地址不相同')

// 弊端:假如创建了100个对象,那么在内存里面就会创建100个sayHi函数,这样会造成内存浪费的问题。其实在内存里面只需要一份sayHi方法,就行了
console.log('p1.sayHi == p2.sayHi的结果:',p1.sayHi == p2.sayHi) // false

console.log([] == []) // false
console.log([].push == [].push); // true
// 数组的两个push,说明两个数组的push方法都是同一个

补充:new做了什么事?

  • 创建了一个新的空对象
  • 让this指向了新对象
  • 执行构造函数的内部代码
  • 将新对象给返回了

注:执行构造函数的代码,就是给新创建的对象添加属性和方法

2、解决构造函数创建对象造成内存浪费的问题

那我们是否可以

function sayHi(){
    console.log("你今天吃了吗");
}

function running(){
    console.log("running");
}
// 这个代码只执行了一次,在内存里面只有一份,
// 这样已经解决了构造函数创建对象造成内存浪费问题,因为方法在内存里面只有一份。
// 这样虽然能解决问题,间接的造成了全局污染的问题

还有没有更好的方法:

var obj = {
    sayHi: function (){
        console.log("你今天吃了吗");
    },
    running: function (){
        console.log("running");
    }
    // ...
}

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

var p = new Person("慧姐", 20);
// console.log(p); // 
// p.sayHi()
p.running();

var p2 = new Person("燕哥", 19);
// p2.sayHi();
p2.running();


// 两个对象访问的sayHi都是同一个,来源于全局里面sayHi函数
console.log(p.sayHi == p2.sayHi); // true
// 这样可以解决全局污染的问题
// 也解决了构造函数创建对象造成内存浪费问题
// 也不这么做,写代码固定了,所以产生了原型

二、原型

  1. 任何函数都有 prototype 属性
  2. 函数的prototype属性值是个对象,我们把这个对象叫做原型(原型对象)
  3. 通过构造函数创建的实例可以直接访问构造函数的prototype属性上的任何成员
  4. 任何对象都有__proto__ 属性, 指向的是构造函数的prototype属性,也就是原型
  5. constructor 属性是原型中自带的属性,指向了当前的构造函数

prototype

// 构造函数
function Person(){

}
// console.dir(Person);
// console.log(Person.prototype);   

// 原型对象
// 这是给原型对象添加color属性
Person.prototype.color = "lime";
Person.prototype.gender = "male";

// 把sayHi方法添加给原型,谁可以访问
// 构造函数创建的实例就可以访问到了
Person.prototype.sayHi = function(){
    console.log("hello");
}

// console.log(Person.prototype);


// 实例对象:p1
var p1 = new Person();
// console.log(p.color); // lime
// console.log(p.gender); // male
// p.sayHi();


// 实例对象:p2
var p2 = new Person();
console.log(p2); // 是个空对象
p2.sayHi();


// p的sayHi和p2的sayHi是同一个函数
// 来源于原型上的sayHi方法
// 这样就解决了内存浪费问题
console.log(p.sayHi == p2.sayHi); // true

** proto **

function Person() {

}
var p = new Person()
console.log(p.__proto__ == Person.prototype) // true
// 从上面的代码可以看出访问原型有两种途径:
//  1. 构造函数通过prototype 属性可以访问到原型
//  2. 实例对象通过__proto__ 属性可以访问到原型

constructor

// constructor是原型中自带的属性,指向了当前的构造函数

function Person(){

}
// console.log(Person.prototype);
console.log(Person.prototype.constructor == Person) // true

var p = new Person();

// 实例对象p访问的原型上的constructor属性,
console.log(p.constructor == Person); // true
console.log(p.constructor == Person.prototype.constructor); // true

用这个图来进一步了解一下原型链 总结:

// 完整的原型三角关系:
//  构造函数、实例对象、原型对象
//    妈妈       孩子      爸爸
//  构造函数与原型对象的关系: 配偶关系
//      构造函数通过prototype 属性可以找到原型
//      原型通过 constructor 属性找到了构造函数

//  构造函数与实例对象的关系:母子
//      构造函数创建了实际对象,但是实例对象不能直接访问到构造函数

//  实例对象与原型对象的关系:父子
//      实例对象可以通过__proto__ 来访问原型
//      实例对象可以通过爸爸的constructor属性间接的访问到构造函数

三、原型链

定义: 任何对象都有__proto__属性,指向的是原型对象,原型对象也是对象,那也有__proto__属性,也就是说原型对象也有自己的原型对象。这样形成的链式结构,叫做原型链

简单理解为: 对象都有爸爸,爸爸也有自己的爸爸, 这样形成的链式结构,叫做原型链

function Person(){

}

var p = new Person();

// 构造函数: Person
// 原型对象: Person.prototype
// 实例对象: p


// console.log(Person.prototype.__proto__ == Object.prototype) // 找爸爸
// console.log(Person.prototype.__proto__.constructor == Object) // 找妈妈


// console.log(Object.prototype.__proto__ == null) // 找爸爸

// 实例对象p的原型链:
//  p ==> Person.prototype  ==> Object.prototype  ==> null

// console.log(Person.__proto__ == Function.prototype) // true
// console.log(Function.prototype.__proto__ == Object.prototype) // true
// 构造函数Person的原型链
// Person ==> Function.prototype ==> Object.prototype ==> null

内置对象的原型链

var arr = new Array()
console.log(arr.__proto__ == Array.prototype) // true
console.log(Array.prototype.__proto__ == Object.prototype) // true

// arr的原型链:
//  arr ==> Array.prototype ==> Object.prototype  ==> null


console.log(Array.__proto__ == Function.prototype) // true
console.log(Function.__proto__ == Function.prototype) // true 
console.log(Function.prototype.constructor == Function) // true Function 的 prototype 和 __proto__ 都指向的了Function.prototype(Function的爸爸是Function.prototype ,Function的妈妈是她自己,自己创造了自己)
console.log(Function.prototype.__proto__ == Object.prototype) // true
console.log(Object.__proto__ == Function.prototype) // ture

// Function 的原型链
// Function ==> Function.prototype ==> Object.prototype ==> null
// Object 的原型链
// Object ==> Function.prototype ==> Object.prototype ==> null
// Array 的原型链
// Array ==> Function.prototype ==> Object.prototype ==> null
// String 的原型链
// String ==> Function.prototype ==> Object.prototype ==> null
// Number 的原型链
// Number ==> Function.prototype ==> Object.prototype ==> null
// Boolean 的原型链
// Boolean ==> Function.prototype ==> Object.prototype ==> null
// Date 的原型链
// Date ==> Function.prototype ==> Object.prototype ==> null
// Math 的原型链
// Math ==> Object.prototype ==> null

最后我们看看完整的原型链

到此我们讲完了原型链

我在网上看到一篇不错的,从null推理的原型链,大家可以再看看 zhuanlan.zhihu.com/p/22989691