概述:javaScript中的原型可以分为两类,对象的原型(隐式原型)和函数的原型(显示原型),它们两个从表面来看最大的区别就是调用方式不同
1.1. 对象的原型
任何对象都有自己的默认原型(隐式原型)
作用:如果通过对象的key去访问它的value时,就会触发get操作。这个操作首先会检查对象中是否有这个属性,如果有的话就使用这个。如果没有的话就会去隐式原型上找
获取隐式原型的方法
var obj = {
name: "Judy",
age: "18"
}
console.log(obj.__proto__) //存在兼容问题
console.log(Object.getPrototypeOf(obj)) //如果真要获取隐式原型,使用这种方式
1.2. 函数的原型
任何函数(除箭头函数)都有自己的prototype属性(显式原型)
函数和函数之间的prototype是不相等的
添加在构造函数上的方法被称为类方法,添加在构造函数的显示原型上的方法被称为实例方法(必须通过实例对象来调用的方法)
作用:
通过new操作符调用函数时,会创建一个新的对象
新对象的隐式原型会指向函数的显示原型
stu1.__proto__ = Student.prototype
获取显式原型的方式:
function Student() {}
console.log(Student.prototype)
1.3. 构造函数的正确写法(构造函数和原型结合)
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.running = function() {}
// 因为new出来的对象隐式原型是指向构造函数的显式原型的,所以可以通过p1调用running
var p1 = new Person("Judy", 18)
p1.running()
1.4. 显式原型上的constructor
constructor默认是指向函数对象本身的
constructor默认是不可被枚举的
1.5. 重写显式原型
函数的显示原型是可以被赋值为其他对象的,但是如果赋值了一个其他对象的话,那么这个函数的constructor就指向的是Object这个函数了,所以需要我们手动的给它添加constructor
function Person() {}
// 先获取一下原本的constructor,看看它的属性描述符都是什么值
console.log(Object.getOwnPropertyDescriptor(Person.prototype, "constructor"))
// 在给Person函数的显示原型赋值了其他普通对象之后,因为新对象中的constructor指向的是Object构造函数,所以需要重新还原一下Person函数的constructor
Person.prototype = {}
// 给Person.prototype重新写一个constructor属性
Object.defineProperty(Person.prototype, "constructor", {
configurable: true,
enumerable: false,
writable: true,
value: Person
})
// 经过上面的操作之后,就完全还原constructor了
console.log(Object.getOwnPropertyDescriptor(Person.prototype, "constructor"))
二、ES5中的继承
继承概述:继承可以分为“属性的继承”和“方法的继承”
一个构造函数为了使自己创建出来的实例对象能够使用自己的属性,所以属性都是加在自身的this中的。所以继承属性的核心思想就是在子类构造函数中调用父类构造函数,并且将子类自身的this通过显示绑定的方法,绑定给父类,使得父类的属性可以添加进子类构造函数的this中,这样子类创建出来的实例对象就可以使用父类构建函数中的属性,从而实现继承
继承方法的核心思想就是,创建一个对象obj,并且使obj的隐式原型指向父类的显示原型,并且让子类的显示原型指向obj即可实现父类方法的继承
2.1. 面向对象的三大特性
封装
继承
多态
2.2. 原型链的概念
原型对象和原型对象之间可以形成链条
在通过对象查找某个属性或者方法时,就会沿着原型链往上找,尽头就是Object的原型对象
2.3. 原型实现继承
通过原型,可以继承父类的各种定义在原型对象上的方法
但是这个方法需要通过new操作符,调用一次父类的函数,所以并不可取。
最终应该使用寄生组合继承的方式
// 原型链实现父类方法的继承
function Person() {}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
function Student () {}
// 先new一个对象出来,再把这个对象赋值给Student的原型,这样在Student的原型上定义方法时,就不会影响Person的原型
var obj = new Person()
Student.prototype = obj
// 必须得等先把new出的Person的实例对象赋给Student的原型之后,再给Student的原型上写属于自己的方法,否则会被覆盖
Student.prototype.studying = function() {
console.log("studying~")
}
// 这样Student通过new操作符构造的实例对象也可以调用Person的方法
var stu1 = new Student()
stu1.running()
stu1.eating()
stu1.studying()
2.4. 借用构造函数
通过借用父类的构造函数,达到子类继承属性的目的
因为父类都是把各个属性都添加进this中的
而在子类通过new操作符创建实例对象时,子类中的this都是指向刚创建的实例对象的
所以可以在调用子类构造函数创建实例对象时,调用父类构造方法,并且把子类中的this绑定给父类
这样父类就会把各个属性都添加到子类的this中
而子类的this指向的又是刚创建的实例对象,所以这样就可以实现属性的继承
// 借用构造函数实现父类属性的继承
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
function Student (name, age, height, sno, score) {
// 1.因为父类中是把各个属性都添加到this中的。
// 2.又因为,在new创建实例对象时,构造函数中的this全部都指向该实例对象,所以可以通过把子类的this绑定给父类this的方 法,使在new子类实例对象时,把父类的属性都添加进子类实例对象中,从而完成属性的继承
Person.call(this, name, age, height)
this.sno = sno
this.score = score
}
Student.prototype.studying = function() {
console.log("studying~")
}
// 这样Student通过new操作符构造的实例对象,就可以调用Person中的各个属性
var stu1 = new Student("Judy", 18, 1.88, 111, 100)
console.log(stu1.name, stu1.age, stu1.height, stu1.sno, stu1.score)
2.5. 寄生组合继承
最终方案
调用封装的inherit函数,并且把要继承的两个函数传进去就可以实现继承
通过继承使用父类定义在原型上的方法的核心就是:创建一个对象作为中间的媒介,使这个对象的隐式原型指向父类的显示原型,使子类的显示原型指向创建的对象的隐式原型。子类在查找该方法时,就可以通过原型链一步步找到父类原型上的方法。
// 封装的寄生式组合式继承函数
function createPrototype(obj) {
function F() {}
F.prototype = obj.prototype
return new F()
}
function inherit(subType, superType) {
// 下列三种方法虽然也可行,但是有兼容性问题
// subType.prototype = Object.create(superType.prototype)
// subType.prototype.__proto__ = superType.prototype
// Object.setPrototypeOf(subType.prototype, superType.prototype)
// 这种方法可以兼容任何浏览器
subType.prototype = createPrototype(superType)
// 添加constructor
Object.defineProperty(subType.prototype, "constructor", {
configurable: true,
enumerable: false,
writable: true,
value: subType
})
}
function Person(name, age, height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function() {
console.log("running~")
}
Person.prototype.eating = function() {
console.log("eating~")
}
functionStudent (name, age, height, sno, score) {
Person.call(this, name, age, height)
this.sno = sno
this.score = score
}
inherit(Student, Person)
Student.prototype.studying = function() {
console.log("studying~")
}
var stu1 = new Student("Judy", 18, 1.88, 111, 100)
stu1.running()
2.6. Object是所有类最终的父类
所以Object上的所有方法,其他的类和对象都是可以使用的
所有的类和对象的原型最终都会指向Object的原型对象,而Object的原型对象最终指向null
2.7. 对象判断方法补充
hasOwnPrototpye
判断自己是否有某个属性(不包括原型上的属性)
var obj = {
name: "Judy",
age: 18
}
obj.__proto__.message = "Hello World"
console.log(obj.hasOwnProperty("name")) //true
console.log(obj.hasOwnProperty("message")) //false
in/ for...in操作符
in操作符是判断某个属性是否在对象的属性中或者是否在对象的原型中
var obj = {
name: "Judy",
age: 18
}
obj.__proto__.message = "Hello World"
console.log("name"inobj) //true
console.log("message"inobj) //true
for...in 操作符是遍历出对象和对象原型上的所有可枚举属性的value
var obj = {
name: "Judy",
age: 18
}
obj.__proto__.message = "Hello World"
for (var key in obj) {
console.log(key)
}
instanceof操作符
instanceof可以判断某个实例对象的原型链上是否有构造函数
判断实例对象和构造函数的关系
function Person() {}
function Student() {}
function Animal() {}
inherit(Student, Person)
varstu1=newStudent()
console.log(stu1instanceofPerson) // true
console.log(stu1instanceofStudent) // true
console.log(stu1instanceofAnimal) // false
isPrototypeOf方法
可以判断某个对象是否出现在了某个对象的原型链上
判断对象和对象的关系
function Person() {}
function Student() {}
function Animal() {}
inherit(Student, Person)
var stu1 = newStudent()
console.log(Person.prototype.isPrototypeOf(stu1)) // true
console.log(Student.prototype.isPrototypeOf(stu1)) // true
console.log(Animal.prototype.isPrototypeOf(stu1)) // false