7-1、面向对象与原型

1.理解原型

1、复用代码 <=> 继承 <=> 原型

复用代码的其中一种方式是继承,在js中继承的实现是通过原型。

2、判断对象是否存在某个属性—— in 操作符

const a ={
    age:1
}
console.log('aAtt' in a)  // true 属性名前加引号!

3、为对象添加原型对象-Object.setPrototypeOf(obj1,obj2)

const a = {
    age:1
}
const b = {
    name:2
}
// 将b设为a的原型,在a找不到的属性会去原型b找,通过原型链向上查找
Object.setPrototypeOf(a,b) //每个对象都有一个原型,每个对象都原型都可以有一个原型,形成原型链
console.log('name' in a)  //true  对象可以直接调用原型的属性 如同调用自己的属性一样 .方式
//  {age: 1}
//  age: 1
//  __proto__:
//  name: 2
//  __proto__:
//  constructor

4、构造器与原型

每个函数都有一个原型对象 => prototype ,该原型对象是函数实例化后的对象的原型
这个原型对象默认只有一个contructor属性,该属性指向该函数本身
实例化后的对象的原型是构造函数的原型对象,而实例化后的对象可以直接调用原型的属性,所以,constructor是构造函数本身

function aa(){
    this.name="wang",
    this.age=11
    return 1;
}
aa.prototype.action=function (){ //函数的原型对象是prototype
    return this.name  //原型内方法可以使用this,this为实例化后的对象
}
console.log('aa.prototype',aa.prototype) //{ action:function(){},constructor:aa函数体 }
console.log('aa.prototype.constructor',aa.prototype.constructor) // aa函数体
    
const obj=new aa()
obj.action(); // 实例化后的对象的原型对象没有名称prototype,是可以直接访问原型对象的
obj.constructor // aa函数体
obj
// {name: "wang", age: 11}
// age: 11
// name: "wang"
//  __proto__:
//      action: ƒ ()
//      add: "青岛"
//      constructor: ƒ aa()
//      __proto__: Object

当构造器函数的方法与原型方法名称重合时,优先使用构造函数内方法。但是不推崇
注意:原型方法只能在实例化后的对象中使用,普通调用函数赋值给对象,使用不了原型方法。
通过构造函数实例化大量对象时,重复拷贝意义不大,且会消耗大量内存。冲这个角度看,在函数原型上创建对象方法时很有意义的,可以使同一个方法由所有对象实例共享

5、js动态特性的副作用

函数由默认原型对象,没有显示的修改原型对象,则只有constructor属性,指向该函数本身。
在实例化对象前后修改原型对象都可以,但给原型对象重新整体赋值,则需要重新实例化对象。
之前实例化的对象保留着对原来的原型对象的引用,可以继续访问。
现在实例化的对象只能访问重新赋值后的原型。
因为实例化对象时,对象原型与函数原型建立引用关系,所以实例化前后修改都可以访问;重写函数原型时,需要重新实例化对象,建立新的引用,否则愿对象只能访问原来的引用。

    function aa(){
        this.name="wang",
        this.age=11
        return 1;
    }

    aa.prototype.action=function (){
        return this.name
    }
    
    const obj=new aa()
    aa.prototype.add="青岛"; 
    console.log(obj) //
    //aa {name: "wang", age: 11}
    //age: 11
    //name: "wang"
    //__proto__:
    //action: ƒ ()
    //add: "青岛"
    
    
    aa.prototype={
        add:"安徽"
    }
    const obj1=new aa()
    console.log(obj1)
    //aa {name: "wang", age: 11}
    //age: 11
    //name: "wang"
    //__proto__:
    //add: "安徽"
    
  • 函数有prototype对象
    • constructor:指向自己(默认只有该属性)
    • 原型属性(手动添加才有)
    • 原型方法(手动添加才有)
  • 实例化对象有proto对象,直接调用原型属性和方法
    • obj.name(对象没有会从原型链中查找)
    • obj.constructor (functioin)

6、通过构造函数实现对象类型 instanceof

instanceof:检测一个实例是否由特定构造函数创建。 可以由多个构造函数创建出来 可能是直属构造函数创建,也可能是由构造函数的原型创建出来。
prototype 属性的值有可能会改变,改变之后为false
constructor:属性是创建实例对象函数的引用。它的存在仅仅是说明该对象是从哪创建出来的,如果该值被重写,原始值就丢了

7、实现继承

将一个对象的实例赋给另一个对象的原型。
A.prototype = new B()
注意:A.prototype中的constructor被改写了,查找对象的constructor找不到就会去原型B对象找,找不到去B的原型找,最后找到了函数B。其实是不对的

const a=new A()
a instanceof A //true instanceof判断函数是否继承原型链上的对象
a instanceof A //true
a.constructor = functio B
这种方式继承的好处是继承函数的原型将实时更新。原型改了,对象中访问的原型属性可随之更改。或者可以选择重新改写原型对象。

8、重写constructor的问题

js配置属性功能 —— Object.defineProperty方法

  • configurable 是否可以修改、删除属性
  • enumerable 是否可枚举,为true时可以在for in循环中出现
  • value 属性值,默认undefined
  • writable 是否可以通过赋值语句修改属性值,可写
  • get 定义getter函数,当访问属性时发生调用,不能与value和writable同时使用
  • set 定义setter函数,当对属性赋值时发生调用,不能与value和writable同时使用
  • Object.defineProperty(对象,'属性名',{配置项内容})
var nin = {}
nin.name = 'yo'
nin.age = 11
Object.defineProperty(nin,'sne',{
    configurable:false, //不允许修改 删除
    enumerable:false, //不可枚举
    value:true, //值是true
    writable:true//可写
})
//解决constructor问题
function Person(){
    this.age='per'
}
Person.prototype.dance=function(){
  
}
function Nin(){
   this.name='nin'
}
Nin.prototype = new Person() //继承
Object.defineProperty(Nin.prototype,'constructor',{
    enumerable:false, //不可枚举
    value:Nin, //Ninja函数
    writable:true //可写
})
const nin = new Nin() 

9、 instanceof操作符

nin instanceof Nin
操作符instanceof用于检测【Nin函数原型】是否出现在实例【nin的原型链中】。

10、当心构造函数的改变

function Nin(){}
const nin = new Nin()
nin instanceof Nin //true
Nin.prototype = {} //重置原型对象,需要重新实例化对象才能获得新原型的引用
nin instanceof Nin //false Nin原始重置了,nin仍然保存之前原型的引用,或者这样理解:Nin原型是{},但是nin的原型是Nin 默认原型,所以不匹配为false

11、class

在js中使用关键字class,底层实现仍然是基于原型继承。

创建class类
class Nin{
    // 构造函数
    constructor(name){
        this.name=name  // 这是给实例初始化的
        a = 1   // 注意!!这里定义的不是在prototype上的属性,而是给实例初始化的
    }
    //原型方法
    swing(){
        return true;
    }
}

var ninja = new Nin('wang') //new 调用类
class语法糖转为es5
//以上转为es5
function Nin(name){
    this.name = name;
}
Nin.prototype.swing = function(){
    return true;
}

var ninja = new Nin('wang') //new 构造函数
静态方法
class Nin{
    // 构造函数
    constructor(name){
        this.name=name
    }
    //原型方法
    swing(){
        return true;
    }

    static compare(nin1,nin2){
        return nin1.name + nin2.name
    }
}

var ninja = new Nin('wang') //new 调用类
var ninja1 = new Nin('liu')

ninja.compare //undefined
Nin.compare(ninja,ninja1) //wnagliu 
//静态方法是类方法,传入两个实例化对象
静态方法转为es5
function Nin(name){
    this.name = name;
}

Nin.compare = function(ninja,ninja1){...}
//函数是第一类对象,所以也有属性,静态方法就是给构造函数添加属性方法

实现继承

es5继承,对所有实例均可访问对方法必须直接添加到构造函数原型上。
将父函数的实例化对象赋给子函数的原型。
但是这会修改constructor属性,所以需要Object.defineProperty方法手动重置constructor属性。

es6继承
//父类
class Nin{
    constructor(name){
        this.name=name
    }
    swing(){
        return true;
    }

    static compare(nin1,nin2){
        return nin1.name + nin2.name
    }
}

//子类
class Son extends Nin{ //extends继承
    constructor(name,wea){
        super(name);
        this.wea = wea;
    }
    weapon(){
        return true+super.swing();
    }
}
var ninja=new Nin('wnag');
    //父类
    class Person{
        constructor(name){
            this.name = name
        }
        age:'1',//原型属性
        showName(){
            console.log(`父类名字:${this.name}`) //原型内能访问this对象
        }
    }
    
    //子类
    class Student extends Person{
        constructor(name,skill){
            super(name) //子类构造函数必须先调用super父类构造函数
            this.skill = skill //子类自己构造函数其他属性
        }
        //子类原型方法与父类原型方法重名时,子类方法优先
        showName(){
            super.showName();//父级的方法执行,此处继承了父类方法
            console.log(`子类名字:${this.name}`)//并且添加了自己的东西
            //若隐藏掉super,则完全是自己的子类方法
            //若不重名,则完全继承父类方法
        }
        showSkill(){
            return `技能:${this.skill}`
        }
    }

    let s = new Student("吾爱博客","逃学")
    //{name: "吾爱博客", skill: "逃学"}
    //__proto__: Person
    //constructor: class Student
    //showName: ƒ showName()
    //showSkill: ƒ showSkill()
    //__proto__:
    //constructor: class Person
    //showName: ƒ showName()
    //__proto__: Object
    console.log(s.skill,s.name) // 逃学 吾爱博客
    s.showName() // 逃学 吾爱博客
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。