JS-原型链

作者: 刘淘 | 来源:发表于2020-06-16 23:17 被阅读0次
    首先我们需要牢记两点:

    ①_ _proto _和constructor属性是对象所独有的;
    ② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有
    _proto __和constructor属性。


    函数的prototype属性指向 函数的原型对象
    对象的_ _proto __属性指向 原型对象
    对象的constructor属性指向 该对象的构造函数


    _ _proto __属性的作用就是 当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的_ _proto _属性所指向的那个对象(父对象)里找,一直找,直到proto属性的终点null,再往上找就相当于在null上取值,会报错。通过 _proto __属性将对象连接起来的这条链路即我们所谓的原型链。

    prototype属性的作用就是 让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype。

    constructor属性的含义就是 指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。

    image.png

    原文链接:https://blog.csdn.net/cc18868876837/article/details/81211729


    1.基础知识

    1.对数组,对象而言,都存在一个_ _proto _的私有属性,称之为原型,可为数组或对象附加更多的能力
    2.对数组而言,
    _proto _依次上溯到Array ,Object;
    对对象而言,
    _proto __只上溯到Object
    3.可使用Object.getPrototypeOf()或proto获取当前成员的原型,使用hasOwnProperty()检测原型成员
    4.可使用Object.create(null)创建无原型的对象(极少使用,完全字面量对象),不可使用hasOwnProperty()检测原型成员

        const nums = [1, 2, 3, 4, 5]
        const user = { name: 'xiaoming' }
        console.log(nums)
        console.log(user)
        console.log(nums.__proto__)
        console.log(user.__proto__)
        console.log(Object.getPrototypeOf(nums)) //获取nums的当前成员原型
        console.log(Object.getPrototypeOf(user)) //getPrototypeOf获取当前成员的原型
        console.log(user.hasOwnProperty('name'))         // true
    

    2.原型方法

    • 对象包含属性和方法,同时也继承其原型(_ _proto __)上的成员
    • 如果对象和其原型同时拥有相同的成员,优先使用对象自身的成员


      image.png

    3.函数原型

    1.函数原型
    • 在js中函数也是对象,除拥有函数专属的prototype属性外,还存在_ _proto _ _属性
    • 函数的本质其实也是对象,_ _proto _ _和prototype也都是对象
    • 通常将实例方法定义在prototype上,实例的_ _ proto _ _指向类的类型prototype
    • 可在_ _ proto _ _定义"类方法"(不推荐),适用于将函数当对象使用时的场景
      image.png
    image.png
    2.函数原型的constructor

    4.对象原型

    • 对象的原型默认指向Object
    • 可以使用Object.getPrototypeOf查看对象的原型
    • this本身和原型是没有关联的,只会指向调用函数本身的对象

    5.类型检测

    • 可使用instanceof检测当前对象是否为指定的类型,如果原型发生改变形成‘继承’关系,则子实例也是其父类或祖先类的类型
    • 可使用isPrototypeOf检测当前对象是否为指定类型的原型,类型检测会按原型链依次上溯直到Object为止
    • 可结合in和hasOwnProperty来检测当前对象是否包含指定的属性,in将检测所有属性(含原型),hasOwnProperty仅检测自有属性。
     // instanceof
        function Child() {}
        function Parent() {}
        function Grandpa() {}
        // 修改原型
        const g = new Grandpa()
        Parent.prototype = g
        const p = new Parent()
        Child.prototype = p
        const c = new Child()
        console.log(c instanceof Child)   // true
        console.log(c instanceof Parent)  // true
        console.log(c instanceof Grandpa) // true
        console.log(p instanceof Parent)  // true
        console.log(p instanceof Grandpa) // true
        console.log(g instanceof Grandpa) // true
        // isPrototypeOf
        const member = {}
        const admin = {}
        const user = {}
        console.log(admin.isPrototypeOf(member))                // false
        console.log(admin.__proto__.isPrototypeOf(member))      // true
        console.log(admin.__proto__ === Object.prototype)       // true
        Object.setPrototypeOf(member, admin)
        console.log(admin.isPrototypeOf(member))                // true
        Object.setPrototypeOf(admin, user)
        console.log(user.isPrototypeOf(admin))                  // true
        console.log(user.isPrototypeOf(member))                 // true
       // in & hasOwnProperty
        const a = { name: 'xiaoming' }
        const b = { url: 'baidu.com' }
        console.log('name' in a)                                // true
        console.log('url' in a)                                 // false
        Object.setPrototypeOf(a, b)
        console.log('name' in a)                                // true
        console.log('url' in a)                                 // true
        console.log(a.hasOwnProperty('name'))                   // true
        console.log(a.hasOwnProperty('url'))                    // false
        // 遍历属性
        for(const key in a) {
          console.log(`in: ${key}`)
          if(a.hasOwnProperty(key)) {
            console.log(`hasOwnProperty: ${key}`)
          }
        }
    

    6.方法借用

    +通过call()、apply()和bind()方法,我们可轻易地借用其它对象的方法,而无须从这些对象中继承它。

    • 有时候在对象中实现某个功能,虽然自身不存在,但可“借用”别的对象中已有的方法
    • 对DOM节点的操作经常会“借用”数组的某些函数来达到需求
    const obj = {
       nums: [1, 2, 3, 4, 5]
    }
    Object.setPrototypeOf(obj, {
       max(nums) {
           nums = nums || this.nums
           return nums.sort(function (a, b) { return b - a })[0]
       }
    })
    console.log(obj.max())
    
    //Dom节点筛选
    const btns = document.querySelectorAll('button')
    //btns类数组借用数组的filter且将参数item=>item.hasAttribute('class')作为filter的回调
    const filterBtns = Array.prototype.filter.call(btns, item => item.hasAttribute('class'))
    /**使用字面量借用方法 字面量是一种遵循JavaScript规则的语法结构,MDN 这样解释:
    在JavaScript中,使用字面量可以代表值。它们是固定值,不是变量,就是在脚本中按字面给出的。
    字面量可以简写原型方法:
    */
    const filterBtns = [].filter.call(btns, item => item.hasAttribute('class'))
    

    7.深度认识_ _ proto _ _

    https://blog.csdn.net/cc18868876837/article/details/81211729

    8.原型继承(7种方式)

    方式1:原型链继承(不推荐)

    缺点:
    无法向父类构造函数传参
    父类的所有属性被共享,只要一个实例修改了属性,其他所有的子类示例都会被影响

    //构造函数
    function Person(age) {
        this.age = age || 18
    }
    Person.prototype.sleep = function () {
        console.log('sleeping')
    }
    //=======================================
    function Programmer() { }
    Programmer.prototype = new Person()
    Programmer.prototype.code = function () {
        console.log('编程人会coding')
    }
    
    let xiaoming = new Programmer()
    xiaoming.code()
    xiaoming.sleep()
    console.log(xiaoming.age)
    
    console.log(xiaoming instanceof Person)
    console.log(xiaoming instanceof Programmer)
    
    console.dir(Object.getPrototypeOf(xiaoming))
    console.dir(xiaoming.__proto__)
    
    
    方式 2:借用构造函数(经典继承)(不推荐)

    优点:

    • 可以为父类传参
    • 避免了共享属性

    缺点:

    • 只是子类的实例,不是父类的实例
    • 方法都在构造函数中定义,每次创建实例都会创建一遍方法
    //构造函数
    function Person(age) {
        this.age = age || 18
    }
    Person.prototype.sleep = function () {
        console.log('sleeping')
    }
    //==============================
    function Programmer(name){
        Person.call(this)
        this.name=name
        this.code=function(){
            console.log('codecodecode')
        }
    }
    let xiaoming=new Programmer('xiaoming')
    console.log(`${xiaoming.name}      ${xiaoming.age}`)
    //方法都在构造函数中定义,每次创建实例都会创建一遍方法
    xiaoming.code() 
    console.log(xiaoming instanceof Programmer)
    // false 只是子类的实例,不是父类的实例
    console.log(xiaoming instanceof Person)
    //只是子类的实例,不是父类的实例
    xiaoming.sleep()  //Uncaught TypeError:xiaoming.sleep is not a function
    
    
    3.组合继承(推荐)

    组合 原型链继承 和 借用构造函数继承
    优点:

    • 可以为父类传参
    • 方法都在构造函数中定义,每次创建实例都会直接继承方法
      缺点:
    • 调用了两次父类构造函数
    //构造函数
    function Person(age) {
       this.age = age || 18
    }
    Person.prototype.sleep = function () {
       console.log('sleeping')
    }
    //==============================
    
    function Programmer(age, name) {
       Person.call(this, age)
       this.name = name
    }
    Programmer.prototype = new Person()
    //通过Programmer.prototype.constructor=Programmer 修正constructor指向,不加此句programmer的构造函//数是Person的 ??
    Programmer.prototype.constructor = Programmer  
    
    let xiaoming = new Programmer(10, 'xiaoming')
    console.log(`${xiaoming.age}   ${xiaoming.age}   ${xiaoming.sleep}`)
    xiaoming.sleep()
    let zhangsanfeng = new Programmer(100, 'zhangsanfeng')
    console.log(`${zhangsanfeng.age}   ${zhangsanfeng.age}   ${zhangsanfeng.sleep}`)
    zhangsanfeng.sleep()
    
    console.log(`${xiaoming instanceof Programmer}   ${xiaoming instanceof Person}`)
    
    
    
    

    4.原型式继承 (不推荐)

    原型 prototype共享了属性和方法
    一个实例修改了原型上的方法,其他都会被影响

    function create(o) {
        function F() { }
        F.prototype = o
        return new F()
    }
    
    let obj = { gift: ['switch', 'Mini'] }
    
    let gift1 = create(obj)
    console.log(gift1.gift)
    
    let gift2 = create(obj)
    console.log(gift2.gift)
    gift2.gift.push('xxx')
    //只在gift2中新增了一个 gift1中也有了变化
    console.log(gift2.gift)
    
    console.log(gift1.gift)
    
    image.png

    5. 寄生式继承(不推荐)

    创建一个仅用于仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
    缺点:
    跟借用构造函数一样,每次创建对象都要创建一次方法

    function createObj(o) {
        //通过Object.create()实现继承的顺序至关重要,必须保证定义原型方法或新建对象在Object.create()之后进行
        const clone = Object.create(o)
        //创建了一个对象,增强对象
        clone.sayName = function () {
            console.log('hi')
        }
        return clone
    }
    

    6.寄生组合继承(最佳)

    子类构造函数继承父类自身的属性和方法,子类原型继承父类原型的属性和方法

    function Person(age) {
        this.age = age || 18
    }
    Person.prototype.sleep = function () {
        console.log('sleeping')
    }
    
    function Programmer(age, name) {
        Person.call(this)  //子类构造函数复制父类的自身属性和方法 
        this.name = name
    }
    
    Programmer.prototype = Object.create(Person.prototype) //子类原型接受父类的原型属性和方法
    
    Programmer.prototype.constructor = Programmer //修复Programmer的构造函数指向 
    
    let xiaoming = new Programmer(16, '小名')
    console.log(xiaoming.name)
    

    7:ES6 extends(最佳)

    //父类 
    class Father {
        constructor(age) {
            this.age = age
        }
        sleep() {
            console.log('sleeping')
        }
    }
    
    class Children extends Father {
        constructor(age, name) {
            super(age)
            this.name = name
        }
        code() {
            console.log('coding')
        }
    }
    let bo = new Children(20, '伯')
    let zhong = new Children(18, '仲')
    console.log(`${bo.name}  ${zhong.name}`)
    console.log(`${bo instanceof Children}  ${bo instanceof Father}`)
    console.log(`${zhong instanceof Children}  ${zhong instanceof Father}`)
    console.log(bo.prototype === zhong.prototype)
    
    image.png

    相关文章

      网友评论

        本文标题:JS-原型链

        本文链接:https://www.haomeiwen.com/subject/qccqxktx.html