原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。
通过该构造函数产生的对象,可以继承该原型的属性和方法。
什么是构造函数?
构造函数是一种特殊的函数,主要用来初始化对象
通过构造函数来快速创建多个类似的对象。
构造函数的约定:
大驼峰命名规则(大写字母开头);
只能由“new”操作符来执行。
说明:
- 使用new关键字调用函数的行为被称为实例化
- 实例化构造函数时没有参数时可以省略()
- 构造函数内部不需要return,返回值即为新创建的对象
- new Object() , new Date() 也是实例化构造函数
内部原理
三段式(主要原理就是函数中的this到底是什么)
- 在函数体最前面隐式的创建this={}
- 执行this.xxx = xxx (this = {xxx:实参})
- 在函数最后隐式的返回 return this
function Person(name,sex,age){
// 1. 在逻辑的最顶端生成一个 let this = {}
// AO { this : {}}
this.name = name
this.sex = sex
this.age = age
// this = {
// name:'tom',
// sex:'male',
// age:18
// }
// return this
}
let p = new Person('tom','male',18)
回到原型
原型本质上是一个对象,这个对象是在构造函数身上的,也称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'
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'
//也可以这样写
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(){
/**
* 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.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)