原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。
通过该构造函数产生的对象,可以继承该原型的属性和方法。
原型本质上是一个对象,这个对象是在构造函数身上的,为prototype,称为原型对象。
原来在构造函数中直接赋给对象实例的值,可以直接赋给它们的原型。
//构造函数的特点:大驼峰。
//构造函数是为了生产对象的,使用new来生产对象
//构造函数通过不停的调用可以产生多个相似且独立的对象
//构造函数和原型的this都指向实例化对象
// Person.prototype --> 原型
// Person.prototype = {} 是祖先
Person.prototype.LastName = 'Zhao'
Person.prototype.say = function(){
console.log('hi');
}
function Person(){
}
let p1 = new Person()
let p2 = new Person()
console.log(p1.LastName); //Zhao 会继承prototype上的属性
原型的应用
提取共有属性
利用原型的特点和概念,可以提取共有属性。
function Car(color,owner){
this.owner = owner
this.color = color
this.height = 1400
this.lang = 4900
this.name = 'BMW'
}
let car = new Car('red','tom')
let car1 = new Car('green','tony')
console.log(car); //Car {owner: 'tom', color: 'red', height: 1400, lang: 4900, name: 'BMW'}
console.log(car1); //Car {owner: 'tony', color: 'green', height: 1400, lang: 4900, name: 'BMW'}
但是每次实例化对象都要执行一次this.height,this.lang,this.name,就造成了代码的冗余。
所以可以通过继承的规则,将它们放入原型上,给原型赋值后,实例化对象就只执行一次代码。
Car.prototype.height = 1400
Car.prototype.lang = 4900
Car.prototype.name = 'BMW'
//也可以这样写
/*
Car.prototype = {
height:1400,
lang:4900,
name:'BMW'
}
*/
function Car(color,owner){
this.owner = owner
this.color = color
}
let car = new Car('red','tom')
let car1 = new Car('green','tony')
console.log(car); //Car {owner: 'tom', color: 'red'}
console.log(car1); //Car {owner: 'tony', color: 'green'}
console.log(car.height); //1400
console.log(car1.name); //'BMW'
把不变的方法,直接定义在prototype对象上,这样所有的对象的实例可以共享这些方法。
constructor属性
每个原型对象里面都有一个constructor属性,也叫构造器
该属性指向原型对象的构造函数
function Car(){
}
let car = new Car()
console.log(Car.prototype);
console.log(car.constructor);
并且可以手动修改constructor的值
原型对象__proto__
对象都有一个属性__proto__ 指向构造函数的prototype原型对象。
简单来说,对象原型指向原型对象
之所以对象能够使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。
__proto__不是js标准属性,所以浏览器用[[prototype]]表示
[[prototype]]和__proto__意义相同
对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数。
Person.prototype.name = 'abc'
function Person(){
}
let person = new Person()
console.log(person.__proto__); //{name: 'abc'}
__proto__可以被修改
Person.prototype.name = 'abc'
function Person(){
/**
* 构造函数有一个定义this的过程
* let this = {
* __proto__ : Person.prototype
* }
*/
}
let person = new Person()
let obj = {
name:'cba'
}
person.__proto__ = obj
// person继承的对象就变成了obj
console.log(person.name); //'cba'
原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装,JavaScript中大多是借助原型对象实现继承的特性。
let Person = {
eyes:2,
mouse:1
}
// 想要Woman和Man两个构造函数都继承Person对象的属性
// 则可以把Person对象放到原型上,也就是说的公共的部分放到原型上,称之为原型继承
function Woman(){
}
//Woman通过原型继承
Woman.prototype = Person
let lily = new Woman()
function Man(){
}
//Man通过原型继承
Man.prototype = Person
let tom = new Man()
按道理prototype里有一个constructor属性,可是这两个原型都没有这个属性
那就需要让它指回构造函数
//Woman通过原型继承
Woman.prototype = Person
//指回原来的构造函数
Woman.prototype.constructor = Woman
//Man通过原型继承
Man.prototype = Person
//指回原来的构造函数
Man.prototype.constructor = Person
现在,想给Woman添加一个方法,只想让Woman有这个方法,那就在Woman的原型上添加
//给女人添加一个生孩子的方法
Woman.prototype.haveBaby = function (){
console.log('生孩子');
}
发现lily有这个方法,而tom也有这个方法,为什么呢?
Woman和Man继承的是同一个原型,它们的原型是一模一样的
Woman.prototype === Man.prototype 为true
根据引用类型的特点,他们指向同一个对象,修改其中一个另一个就会受到影响。他们的原型对象都指向Person,修改就会直接修改到Person
怎么解决对象一样,而结构不一样呢?
可以使用深拷贝,让它们两个的原型都等于JSON.parse(JSON.stringify(Person))
也可以使用构造函数,因为构造函数new出来的对象的特性就是结构一样,但是对象不一样。
//用构造函数的形式
function Person(){
this.eyes = 2
this.mouse = 1
}
function Woman(){
}
//Woman通过原型继承
Woman.prototype = new Person()
//指回原来的构造函数
Woman.prototype.constructor = Woman
//给女人添加一个生孩子的方法
Woman.prototype.haveBaby = function (){
console.log('生孩子');
}
let lily = new Woman()
function Man(){
}
//Man通过原型继承
Man.prototype = new Person()
//指回原来的构造函数
Man.prototype.constructor = Person
let tom = new Man()
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,将原型对象的链状结构关系称为原型链。
在 JavaScript 中,每个对象都有一个原型对象。这个原型对象又有自己的原型对象,它们形成了一个原型链。
Object.prototype是绝大多数对象的最终原型对象
function Grand(){
}
let grand = new Grand()
Father.prototype = grand
function Father(){
}
let father = new Father()
Son.prototype = father
function Son(){
}
let son = new Son()
/*
son.__proto__指向Son.prototype
Son.prototype指向father
father.__proto__指向Father.prototype
Father.prototype指向grand
grand.__proto__指向Grand.prototype
Grand.prototype__proto__指向Object.prototype
Object.prototype.__proto__指向为null
原型链和作用域链一样,是一层一层进行查找的
*/
查找规则:
-
当访问一个对象的属性或方法时,首先查找对象本身有没有该属性或方法
-
如果对象本身没有这个属性或方法,则去它的原型对象中查找,也就是__proto__指向的prototype
-
如果原型对象中也没有这个属性或方法,它会继续查找原型对象的原型对象
-
依次类推直到到达原型链的末尾(Object.prototype)
除了原型继承还有一些继承的方式,留到下一篇文章来讲。