吊打 JavaScript 之从原型到原型链

189 阅读5分钟

一、说一说原型模式

每个构造函数都有一个 prototype 原型属性,这个属性它是一个指针,指向一个对象,而这个对象的用途是可以由特定类型的所有实例共享的属性和方法。则这个 prototype 就是通过调用构造函数而创建的那个对象实例的原型对象。

原型对象的好处就是让所有对象实例共享它所包含的属性和方法。

function Person(){
	
}

Person.prototype.name="掘金"
Person.prototype.age=18

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

var person1 = new Person()
person1.sayName() //掘金
var person2 = new Person()
person2.sayName() //掘金

console.log(person1.sayName === person2.sayName)

由上述代码可知,构造函数为一个空函数,sayName()方法和所有属性直接添加到了Person的prototype属性中。


调用构造函数来创建新对象,这个新对象会具有相同的属性和方法。但是和构造函数不同的是,这个新对象的属性和方法是所有实例共享的。


就是,person1和person2访问的都是同一组属性和同一个sayName()函数。

二、什么是原型对象

只要创建一个新函数,其内部就会创建一个 prototype 属性,这个属性指向函数的原型对象。而所有原型对象都会自动获得一个 constructor

构造函数 属性,这个属性指向prototype属性所在函数的指针。


三、原型继承


所有JavaScript对象都从原型继承属性和方法。日期对象继承来自Date.prototype。数组对象继承来自Arrray.prototype。

Person对象继承来自Person.prototype。Object.prototype位于原型继承链的顶端。

日期对象,数组对象和 Person 对象都继承来自Object.prototype。

向对象添加属性和方法

需求一,当需要向所有给定类型的已有对象添加新属性,或者是方法。

需求二,当需要向对象构造器添加新属性,或者是是方法。

使用prototype属性

JavaScript 中的 prototype 属性允许你为对象构造器添加新属性,或者是方法。

function Person(firstName,lastName,age,eyecolor){
	this.firstName=firstName;
	this.lastName=lastName;
	this.age=age;
	this.eyecolor=eyecolor
}

Person.prototype.form='掘金';

function Person(firstName,lastName,age,eyecolor){
	this.firstName=firstName;
	this.lastName=lastName;
	this.age=age;
	this.eyecolor=eyecolor
}

Person.prototype.name=function () {
	return this.firstName + " " + this.lastName
}

创建了构造函数后,其原型对象会取得 constructor 属性,至于其他方法,都是从 Object 继承来的,当调用构造函数创建一个新实例后,该实例的内部包含一个指针,指向构造函数的原型对象。

这个指针就叫 prototype ,每个对象上都有一个属性叫 __proto__。注意的是这个指针存在于实例与构造函数的原型对象之间,不是存在于实例与构造函数之间。

你可以把Person构造函数,Person 的原型属性,和 Person 的两个实例之间的关系结构画出来分析分析。


一个Person构造函数有一个 prototype 。指向了原型对象,Person Prototype。

一个 Person Prototype 中有 constructor,name,age,job,sayName。而Person.prototype.constructor 又指回了 Person。


原型对象中除了包含一个constructor属性外,还有后来添加的其他属性。


Person的每个实例都有一个内部属性,该属性仅仅指向了Person.prototype,严格说,它们和构造函数没有直接关系。


构造函数-原型对象-实例关系图By@若川

重点之一,当调用构造函数创建一个新实例后,该实例的内部将包含一个指针,指向构造函数的原型对象,这个指针叫[[Prototype]]。在每个对象上都支持一个属性__proto__。


对象中的__proto__属性

对象中的__proto__属性在所有实现中都是无法访问到的,但是可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系。

let person1 = new Person()
let person2 = new Person()

console.log(Person.prototype.isPrototypeOf(person1))) //true
console.log(Person.prototype.isPrototypeOf(person2))) //true

person1和person2中内部有一个指向Person.prototype的指针,返回就是true了。

在ECMAScript5中增加了一个新的方法,叫

Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。

代码

console.log(Object.getPrototypeOf(person1) === Object.getPrototypeOf(person2)) //true
console.log(Object.getPrototypeOf(person1).name)  //掘金

确认Object.getPrototypeOf()返回的对象实际是这对象的原型,使用Object.getPrototypeOf()可以方便地取得一个对象的原型。

代码调用过程,调用对象的某个属性时,会首先搜索从对象实例本身开始,如果找到了给定名字的属性,则返回该属性的值,如果没有找到。


会第二次搜索,从指针指向的原型对象开始,在原型对象中查找给定名字的属性,如果在原型对象中查找具有给定名字的属性,就返回该属性值。

如何判断一个属性是否存在于实例中呢,还是存在于原型中呢?

我们可以使用hasOwnProperty()方法来给指定属性判断是否存在于对象实例中,存在对象实例中时,返回值为true。

function Person(){
	
}

Person.prototype.name="掘金"
Person.prototype.age=18


let person1 = new Person()console.log(person1.hasOwnProperty('name')) //false
person1.name='hello'
console.log(person1.name) //hello  --来自实例
console.log(person1.hasOwnProperty('name')) //true

使用in操作符

in操作符用来判断在通过对象能够访问给定属性时,返回为true。无论是该属性是在实例中还是在原型中。

同时使用hasOwnProperty()方法和In操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中。

function hasPrototypeProperty(object,name){
	return !object.hasOwnProperty(name) && (name in Object)
}

上述代码用来判断属性存在于原型中。in操作符通过对象能够访问到属性就返回true,hasOwnProperty()只要属性存在于实例中时才返回true。

所以只要in操作符返回true而hasOwnProperty()返回false,就可以确定属性是原型中的属性。

要取得对象上所有可枚举的实例属性,可使用Object.keys()方法,这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。


function Person(){
	
}

Person.prototype.name = 'juejin'
Person.prototype.age = 18
Person.prototype.job = 'web'

var Keys = Object.keys(Person.prototype)
console.log(keys) // ["name", "age", "job"]

var person1 = new Person()
person1.name ='data'
person1.age= 18
console.log(Object.keys(person1))  // ["name", "age"]

可以看出,Object.keys()方法保存的是一个数组,循环顺序的出现,如果有Person的实例调用,那么出现的实例中的属性。


如果要出现所有实例属性,无论它是否可以枚举,都可以使用

Object.getOwnPropertyNames()方法。

var keys = Object.getOwnPropertyNames(Person.prototype)
console.log(keys) // ["constructor", "name", "age", "job"]

返回结果中包含不可枚举的constructor属性。

基于原型的常见的几种继承方式

juejin.cn/post/684490…