原型链

187 阅读5分钟

面向对象三大特征: 封装 、 继承 、 多态

封装 :   把代码放入对象的方法中
继承 :   一个对象(子对象) 拥有 另一个对象(父对象) 所有的成员(属性和方法) js的重点: 继承
多态 :  一个对象 在不同情况下的不同状态(js语言基本不涉及多态,主要是java)

何为原型继承?   把父对象 作为子对象构造函数的原型对象.

语法:子对象.prototype = 父对象

        // 父对象
        let father = {
            adress: {
                adress: '剑气长城',
                price: '无价'
            },
            love: {
                name: '小米粒',
                price: '无价'

            }
        }
        // 子对象构造函数
        function Son(name, age) {
            this.name = name
            this.age = age
        }
        // 原型继承: 把父对象 作为子对象构造函数的原型对象
        Son.prototype = father
        let s1 = new Son('平安', 18)
        let s2 = new Son('宁姚', 17)
        console.log(s1, s2);

原型链

原型链 : 每一个对象都有自己的原型, 而原型也是对象, 也有自己的原型, 以此类推, 形成链式结构.
解释一下上面这句话: 每一个对象都有自己的原型(__proto__),对象.__proto__,对象.__proto__,指向了原型对象, 原型对象也是对象, 所以她也有自己的原型 __proto__, 依次类推, 形成了链式结构.

对象访问原型链规则:  当访问实例对象的成员(属性和方法时),先访问自身的, 自身没有就去找原型, 原型也没有, 就找原型的原型.以此类推直到原型链终点(null), 如果还找不到, 如果访问的是属性, 则获取Undefined, 如果找的是方法没找到, (假设找的是Son.learn()这个方法,Son.learn属性返回的是Undefined, 所以Son.learn() == undefined() , undefined没有()语法, 从而报错), 报错显示的是Son.learn is not a function.

以下面这段代码来画出原型链的图:

       //(1)构造函数
       function Person(name,age){
           this.name = name
           this.age = age
       }

       //(2)原型对象
       Person.prototype.love = function(){
           console.log('cxp')
       }
       
       //(3)实例对象
       let p1 = new Person('陈平安',19)

image.png 有一些构造函数是我们自己定义的, 有些是内置的,我们有个最大的构造函数function Object(), 不需要我们写,系统给我们内置好了 每一个对象(数组、字符串等)的原型链都会经过Object.prototype,因为原型对象是创建构造函数时, 系统帮忙创建的,而Object又已经是最大的构造函数,所以这里的系统就可以理解为是Object, 即原型对象是创建构造函数时, Object这个构造函数帮忙创建的,所以最大的构造函数Object,其原型对象Object.prototype都会被不同类型(数组、字符串、Number、Boolean、Date等)的对象的原型链经过.

面试题
原型链作用:   继承用的
js用什么技术实现面向对象的继承:   原型链

补充点额外知识

  1. DOM对象的原型链

image.png

2. instanceof(关键字):   检测构造函数的原型在不在实例对象的原型链中.
语法:实例对象 instanceof 构造函数
注意:instanceof的左边一定要是实例对象.
不能这么写
'abc' instanceof String 因为abc是值类型,除非改为new String('abc') instanceof String
可以这么写 let arr = [10, 20, 30 ]
arr instanceof Array :// 底层原理(new Array([10, 20, 30 ]已经把arr变成实例对象了)
面试点instanceof运算符原理:   检测右边构造函数原型 在不在 左边实例对象的原型链中.
应用:   封装某些函数的时候,为了限制参数的数据类型。可以用instanceof做一个判断.

        //写一个数组,把数组翻转成字符串
        function reverseStr(arr) {
            //判断用户传的是不是arr
            if (arr instanceof Array) {
                //(1)把数组翻转
                arr.reverse()
                //(2)数组元素拼接成字符串
                console.log(arr.join(''))

            } else {
                console.log(arr)
            }
        }
        reverseStr(['夏', '华', '爱', '我']) //我爱华夏 可以翻转
        reverseStr(20) //参数错误 不能翻转,但是不会报错
    </script>

3. 构造函数和原型函数的this都指向实例对象

先验证构造函数里的this指向:

  <script>
    let that
    // 构造函数
    function tryHard(uname) {
      that = this
      this.uname = uname
    }
    // 实例对象 ldh   
    // 构造函数里面的 this 就是  实例对象  ny
    const ny = new tryHard('陈平安')
    console.log(that === ny) //true 验证了构造函数里的this指向实例对象
  </script>

再验证原型对象里的this指向:

  <script>
    let that
    // 构造函数
    function tryHard(uname) {
      //that = this
      // console.log(this)
      this.uname = uname
    }
    // 原型对象里面的函数this指向的还是 实例对象 ny
    tryHard.prototype.xixi = function () {
      that = this
    }
    // 实例对象 ny   
    // 构造函数里面的 this 就是  实例对象  ny
    const ny = new tryHard('陈平安')
    ny.xixi()                 // 先调用一次,执行原型对象里的代码, 进行赋值
    console.log(that === ny) //true 验证了原型对象里的this也指向实例对象
  </script>

4. arguments关键字: 获取函数所有的 实参,只能用在函数里哦

argument获取的实参都放在了数组里,是一个伪数组.

        function fn(a, b) {
            return (arguments)
        }
        console.log(fn(10, 20, 30, 40, 50));

image.png

这个伪数组的原型指向Object, 而操作数组的一些常用方法并不在Object里,所以伪数组无法使用数组的一些方法. 应用 : 用于参数数量 不确定的函数.
例如: arr.push()、Math.max() 这些方法可以传很多个参数,在函数内部就需要用 arguments 来获取所有的实参. 小技巧: 当我们将鼠标放在传参的函数上,vscode会出现一个特殊的提示...,看到这个表示,就可以认为这个函数传的参数不限制个数.

5.剩余参数(rest) : 获取函数 剩余的实参
语法: a,...b
意思是把传进来的实参, 第一个实参给a, 剩下的全部给b, b存的是一个真数组, 如果把a省略, 则几乎和arguments功能相同, 只是一个得到的是真数组, 一个是伪数组而已, 剩余参数(...)只能写在最后位置.
6. var const let
var的特点 :
预解析: var声明提前到当前作用域最前端

        console.log( num )
        var num = 10
        console.log( num )

上述代码实际执行顺序是(即预解析了num):

        var num
        console.log( num )
        num = 10
        console.log( num )

没有块级作用域: 分支和循环里面的变量都是全局

目前var在前端里基本不怎么用.

const 和 let 的特点:
相同点:

  • 变量一定要先声明,才能使用
  • 有块级作用域: 分支和循环里面的变量都是局部的 不同点:
  • let 变量, 可以修改
  • const 常量, 不可修改, 只有在声明的时候进行赋值

面试题 /* for-in与for-of区别
1. 功能不同 for-in : 会遍历下标和元素 for-of : 只会遍历元素
2. 原型不同 for-in : 会遍历原型中的属性 for-of : 不会遍历原型的属性
3. 数据类型不同 for-in : 能遍历数组和对象 for-of : 只能遍历数组

    <script>
        //数组
        let arr = [10, 20, 30] //下标0 1 2
        Array.prototype.cxp = 521
        //for-in
        for (let key in arr) {
            console.log(key) //属性名
            console.log(arr[key]) //属性值    
        }
        // //for-of
        for (let item of arr) {
            console.log(item)
        }
    </script>