原型及继承
原型
概述
原型是一个公共的对象空间,它里面可以存储对应的公共属性及方法。
构造函数的缺陷
function Person(name){
this.name = name
this.sayHello = ()=>{
console.log('hello')
} //this.sayHello = new Function('console.log('hello')')
}
//实例化对象
let person1 = new Person('jack')
let person2 = new Person('jack')
console.log(person1 == person2) //false
console.log(person1.name == person2.name) //true
console.log(person1.sayHello == person2.sayHello) //false (每次new的时候对应的函数空间会被开辟)
- 构造函数内如果去指定方法那么对应的每次实例化都会开辟专门的函数空间去存储对应的操作代码。那么就意味着我实例化的对象越多,需要开辟的函数就越多,而这些函数内的操作都是一致,这就会造成对应的资源浪费。那么就会损耗对应的效率。那么解决这个问题我们就可以利用一个公共空间去存储对应的函数,那么这些找到的函数的地址都是一个。而这个公共空间的名字就称为原型。
一般函数存储在原型上,属性存储在构造函数内。
函数的原型(prototype)
prototype是函数内的一个公共空间,它是一个对象。它属于函数的本身,它被称为显式原型,构造函数也是函数所以它同样具备。
示例
//构造函数
function Person() {
}
console.log(Person.prototype) //对象
//这个里面具备一个属性 constructor 构造器 指向对应的构造函数的
console.log(Person.prototype.constructor == Person)
解决构造函数的缺陷就是将对应的函数存放在对应的原型上
//构造函数
function Person() {
}
console.log(Person.prototype) //对象
//这个里面具备一个属性 constructor 构造器 指向对应的构造函数的
console.log(Person.prototype.constructor == Person) //true
//往构造函数的原型上存放内容
Person.prototype.sayHello = function(){
console.log('hello')
}
let person1 = new Person()
let person2 = new Person()
console.log(person1 == person2) //false
//实例对象访问对应的原型上内容 可以直接点 实例对象.原型上的属性名
console.log(person1.sayHello == person2.sayHello) //true
注意事项
- 函数存储在原型上,属性存储在构造函数内。
- 原型上具备constructor属性 这个属性指向对应的构造函数
- 原型上的内容使用实例对象可以直接访问
- class中跟constructor一级的函数自动存放在原型上
对象的原型 (__proto__)
__proto__是属于对象的原型,实例对象也是对象所以它同样存在。它指向其构造函数的原型,它被称为隐式原型。
示例
//__proto__是对应的对象的一个公共空间
console.log(new Person().__proto__)
//对象的原型指向其构造函数的原型
console.log(new Person().__proto__ === Person.prototype) //true
实现new方法
//new方法实现
//传入对应的构造函数 及参数
function myNew(fn, ...arg) {
//自动构建对象 (将当前的对象原型指向对应的构造函数)
let obj = {}
obj.__proto__ = fn.prototype //显示类型
//传入参数执行构造函数 (填充属性 属性赋值)将当前构造的对象替代里面的this
fn.apply(obj,arg) //属性赋值
//自动返回构造的对象
return obj
}
let newPerson = myNew(Person)
注意事项
__proto__ 指向其构造函数的prototype
__proto__ 非JavaScript标准(浏览器支持)
问题
对象的原型为__proto__ ,它指向对应的构造函数的prototype。这个构造函数的的prototype也是一个对象,那么它同样也具备对应的__proto__ ,那么对应构造函数的原型对象的__proto__ 又指向谁?
Object.prototype.say = ()=>{
console.log('说话')
}
//class是一个代码块 它其实是构造函数基础上进行了封装
class Animal {
constructor() {
}
//存储在原型上
sayHello() {
console.log('hello')
}
}
//基于继承关系 class的extends继承 其实就是将当前的子类原型指向对应的父类原型
class Son extends Animal {
constructor() {
super()
}
print(){
console.log('打印')
}
}
let son = new Son()
//指向其构造函数的prototype
console.log(son.__proto__)
//指向父类构造函数的prototype
console.log(son.__proto__.__proto__)
//指向Object构造函数的prototype
console.log(son.__proto__.__proto__.__proto__)
//指向null
console.log(son.__proto__.__proto__.__proto__.__proto__)
对应的指向从当前的构造函数的prototype 一直到 null为至 而这个指向的过程其实就原型链查找的过程
原型链
在原型上向上查找属性的过程构成的链条(不断在__proto__查找) 称为原型链
原型链的查找过程
- 查找自身的
__proto__是否具备属性找到就返回属性值 - 不断向上查找对应的父类构造函数的prototype是否具备这个属性 找到返回属性值
- 一直查到Object的prototype上是否具备这个属性 找到返回属性值
- 如果Object的prototype上还没有找到就会去找null 返回undefined
原型链不包含对象属性赋值操作
- 对象的属性赋值属性存在就进行替换
- 对象的属性赋值属性不存在就先进行开辟 再进行赋值
示例
Object.prototype.username = '你好'
Object.username = '世界' //对象属性赋值
//class是一个代码块 它其实是构造函数基础上进行了封装
class Animal {
constructor() {
this.age = 20
}
//存储在原型上
password = 123
static username = "吃饭" //静态属性赋值
}
Animal.age = 18 //对象属性赋值操作
Animal.prototype.password = 456
new Animal().age = 30//对象属性赋值
//基于继承关系 class的extends继承 其实就是将当前的子类原型指向对应的父类原型
class Son extends Animal {
constructor() {
super()
}
age = 18
}
new Son().password = 18
Son.sex = '女'
let son = new Son()
son.email = "123@123.com"
console.log(son.age)//18 自身的原型上
console.log(son.username)//您好 object的原型上
console.log(son.password)//123 父类原型上
console.log(son.sex)//undefined 找到了null 返回undefined
console.log(son.email) //123@123.com 跟原型链无关
//函数属性赋值 和原型链无关 对象赋值和原型链无关
图示
注意事项
- 函数对象的
__proto__指向Function的prototype - Function的prototype的
__proto__指向Object.prototype - Object.prototype指向null 返回undefined
instanceOf 判断引用数据类型的类型
示例
console.log(new String('123') instanceof String ) //true
console.log(new String('123') instanceof Object ) //true
实现
循环
//instanceOf的原理是判断当前的构造函数是否为原型链上
function myInstanceOf(obj,constructor){
while(obj.__proto__){
//判断当前的原型上是否存在对应的constructor
if(obj.__proto__.constructor == constructor){
return true
}
obj = obj.__proto__
}
return false
}
递归
//递归实现
function myInstanceOf(obj,constructor){
if(!obj.__proto__){ //找到null 返回false
return false
}
//找到了返回true
if(obj.__proto__.constructor == constructor){
return true
}else{
//没找到继续找
return myInstanceOf(obj.__proto__,constructor)
}
}
重构数组的高阶函数
Array里面的高阶函数都存在于对应的原型上
var str = 'abc'
//字符串调用forEach
Array.prototype.forEach.call(str,v=>console.log(v))
//对象调用的方法都是原型方法 类调用的方法都是静态方法
对象调用的方法都是原型方法 类调用的方法都是静态方法
forEach Map重构
Array.prototype.myForEach = function(callback){
//this指向调用方法的数组
//判断callback
if(typeof callback != 'function'){
console.log(`${arguments[0]} is not function`)
}
//遍历
for(var i=0;i<this.length;i++){
if(i in this){
//执行callback
callback(this[i],i,this)
}
}
}
Array.prototype.myMap = function(callback){
//this指向调用方法的数组
//判断callback
if(typeof callback != 'function'){
console.log(`${arguments[0]} is not function`)
}
//准备返回的数组
let result = new Array(this.length)
//遍历
for(var i=0;i<this.length;i++){
if(i in this){
//执行callback
result[i] = callback(this[i],i,this)
}
}
return result
}
reduce 重构
Array.prototype.myReduce = function(callback,initValue){
//this指向调用方法的数组
//判断callback
if(typeof callback != 'function'){
console.log(`${arguments[0]} is not function`)
}
let initIndex = 0
//是否传入的initValue
if(arguments.length < 2){ //未传入
//判断是否为空
if(!Object.values(this).length){
throw new Error('计算空数组必须传入初始值')
}
//将第一个值作为初始值
initValue = Object.values(this)[0]
initIndex = this.indexOf(initValue) + 1
}
//遍历
for(var i=initIndex;i<this.length;i++){
if(i in this){
//执行callback
initValue = callback(initValue,this[i],i,this)
}
}
return initValue
}
filter重构
Array.prototype.myFilter = function(callback){
//this指向调用方法的数组
//判断callback
if(typeof callback != 'function'){
console.log(`${arguments[0]} is not function`)
}
let result = []
//遍历
for(var i=0;i<this.length;i++){
if(i in this){
//执行callback 判断是否满足条件
callback(this[i],i,this) ? result.push(this[i]) : ""
}
}
}
some 和 every
Array.prototype.mySome = function(callback){
//this指向调用方法的数组
//判断callback
if(typeof callback != 'function'){
console.log(`${arguments[0]} is not function`)
}
//遍历
for(var i=0;i<this.length;i++){
if(i in this){
//执行callback
if(callback(this[i],i,this)){
return true
}
}
}
return false
}
Array.prototype.myEvery = function(callback){
//this指向调用方法的数组
//判断callback
if(typeof callback != 'function'){
console.log(`${arguments[0]} is not function`)
}
//遍历
for(var i=0;i<this.length;i++){
if(i in this){
//执行callback
if(!callback(this[i],i,this)){
return false
}
}
}
return true
}
flat 扁平化方法(重构)
继承
概述
继承主要是子类继承父类的非私有属性和方法
继承的实现
class的继承使用extends关键词实现
class Person{
constructor(){}
static run(){
console.log('跑')
}
}
class Son extends Person{
constructor(){
super() //调用父类构造
}
}
Son.run() //继承的静态方法
特点
extends关键词可以继承对应的静态属性和方法
原型链继承
通过对应的原型指向来实现继承(将子类的原型指向父类对象)
function Animal(name,age){
this.name = name
this.age = age
}
Animal.prototype.sayHello = ()=>{console.log('hello')}
function Person(name,age,sex){
this.sex = sex
}
//原型链继承
Person.prototype = new Animal()
缺点
- 不能进行初始化构造函数内的属性,初始值不能指定
优点
- 可以继承原型上的所有内容
对象冒充
在子类的构造函数内调用父类的构造(传入当前的this)
function Animal(name,age){
this.name = name
this.age = age
}
Animal.prototype.sayHello = ()=>{console.log('hello')}
//对象冒充 在对应的子类的构造函数内调用父类构造
function Person(name, age, sex) {
//调用父类构造函数
Animal.call(this,name,age)
this.sex = sex
}
let person = new Person('jack',18,'男')
console.log(person)
console.log(person.name) //jack
person.sayHello() //无法继承原型上面的内容
缺点
- 无法继承原型上的内容
优点
- 可以继承构造函数内的所有属性及可以初始化对应的属性
组合继承
原型链继承+对象冒充
function Animal(name,age){
this.name = name
this.age = age
}
Animal.prototype.sayHello = ()=>{console.log('hello')}
//组合继承 对象冒充 + 原型链继承
function Person(name, age, sex) {
//调用父类构造函数 对象冒充
Animal.call(this, name, age)
this.sex = sex
}
//原型链继承 Person继承Animal
Person.prototype = new Animal()
let person = new Person('jack', 18, '男')
console.log(person) //原型会存在重复的属性 且值为undefined
console.log(person.name)
person.sayHello()
缺点
在对象的原型上会存在重复的属性 且值为undefined
优点
解决了对象冒充和原型链继承的不足
寄生组合继承
寄生继承 + 对象冒充
function Animal(name,age){
this.name = name
this.age = age
}
Animal.prototype.sayHello = ()=>{console.log('hello')}
// 寄生组合继承
// 利用对象冒充 + 寄生继承
function Person(name, age, sex) {
//调用父类构造函数 对象冒充
Animal.call(this, name, age)
this.sex = sex
}
//寄生继承 Person继承Animal
let prototype = Object.create(Animal.prototype)
prototype.constructor = Person
Person.prototype = prototype
let person = new Person('jack', 18, '男')
console.log(person) //原型会存在重复的属性 且值为undefined
console.log(person.name)
person.sayHello()
缺点
无法继承静态的属性和方法
优点
解决所有其他继承存在的问题