prototype原型链.md 5.7 KB

js原型

#tag prototype 原型 proto

原型对象

#d 何为原型对象
原型对象是每个函数都有的属性, js中的函数会有一个protorype属性,
这个属性是一个对象, 这个对象有一个属性为constructor,意为构造函数.
专门指向函数本身. 在原型对象中, 存放着构造函数的公共属性和方法.

#l(构造函数)

#e 原型示意 构造函数 FN

function FN(){
    this.name = 'p'
    this.age = 18
}
FN.prototype.say = function (){
    console.log(`hello my name is ${this.name}`)
}

此时构造函数 FN 的原型对象可以视作如下内容

属性
constructor FN
proto Object
公共属性
name p
age 18
公共方法 say
let fn = new FN()
fn.say() // hello my name is p

#d 原型对象作用

  1. 存放构造函数的公共属性和方法
  2. 用于构建实例对象
  3. 构造函数的实例对象, 都会共享原型对象中的属性和方法

原型链

#d 什么是原型链
在js中, 每个对象都有一个__proto__的属性,
这个属性指向了该对象的[原型对象], 但是这个[原型对象]本身是一个对象,
所以它也有[原型]属性, 所以我们将之称为[原型链].
这个链条的最终会指向Object对象, Object也会有一个[原型]属性,
但是这个属性为null

#e 原型链示意 下面是一个简单的原型链示意图

function FN(){}
FN.prototype.say = function (){}
let fn = new FN()
// fn实例的原型
fn.__proto__ === FN.prototype // true
// FN对象的原型
FN.prototype.__proto__ === Object.prototype // true
// Object对象的原型
Object.prototype.__proto__ === null // true

#d 原型链作用

  1. 实现继承效果
    当你试图访问一个对象的属性时:
    如果在对象本身中找不到该属性,就会在原型中搜索该属性。
    如果仍然找不到该属性,那么就搜索原型的原型,
    以此类推,直到找到该属性,或者到达链的末端,
    在这种情况下,返回 undefined。
  2. 构造函数的实例对象, 共享原型对象中的属性和方法
    可以直接通过原型链访问修改原型的属性和方法(不建议用这种邪道方法修改)

#e 原型链应用示例

function FN(){}
FN.prototype.say = function (){
console.log(`hello`)
}
let fn = new FN()
fn.say() // hello
fn.say === fn.__proto__.say // true
fn.say === FN.prototype.say // true
// 无法在 object 对象中找到
fn.say === Object.prototype.say // false
fn.age = 18
console.log(fn) // FN { age: 18 }
let fn1 = new FN()
console.log(fn1.age) // undefined
fn1.say() // hello
// 修改原型
FN.prototype.say = function (){
    console.log(`hello my age is ${this.age}`)
}
fn.say() // hello my age is 18
fn1.say() // hello my age is undefined
FN.prototype.age = 19
// fn1 中无法找到age属性, 所以用fn1.age访问到的是FN.prototype.age属性
console.log(fn1.age) // 19
fn1.say() // hello my age is 19
// 修改fn1中的的age属性, 之后在访问该属性, 访问的是fn1.age属性
fn1.age = 20
console.log(fn1.age) // 20

#d 原型链的缺点

  1. 引用类型属性, 会被所有实例共享
  2. 构造函数的实例对象, 共享原型对象中的属性和方法, 会导致原型对象中的属性和方法被修改

#c 继承效果
我们可以通过原型链实现继承效果, 父类中的属性和方法, 都会继承给子类.

#e 继承

  1. 父类构造函数

    function Person(name){
    this.name = name
    this.say = function (){
        console.log(`hello my name is ${this.name}`)
    }
    }
    let p = new Person('p')
    p.say() // hello my name is p
    
  2. 子类构造函数

    
    function Student(name, age){
    this.age = age
    }
    Student.prototype = new Person()
    let s = new Student('s', 18)
    // 父类属性会被继承给子类, 但是name属性暂时未赋值
    s.say() // hello my name is undefind
    console.log(s) // Student { age: 18 }
    
  3. 父类属性赋值

    Student.prototype.name = 's'
    s.say() // hello my name is s
    
  4. 另一种写法
    该写法, 会将父类构造函数中的属性和方法, 赋值给子类构造函数的原型对象中,
    其属性与方法, 都是子类实例对象所共享的, 不会影响父类构造函数中的属性和方法

    function Student2(name, age){
    Person.call(this, name)
    this.age = age
    }
    let s2 = new Student2('s2', 19)
    s2.say() // hello my name is s2
    console.log(s2) // Student2 { age: 19, name: 's2', say: [Function (anonymous)] }
    
  5. 组合继承
    父类的构造函数被调用了两次(创建子类原型时调用了一次,创建子类实例时又调用了一次),
    导致子类原型上会存在父类实例属性,浪费内存。

    function Parent(value) {
    this.value = value;
    }
    
    Parent.prototype.getValue = function() {
    console.log(this.value);
    }
    
    function Child(value) {
    Parent.call(this, value)
    }
    
    Child.prototype = new Parent();
    
    const child = new Child(1)
    child.getValue();
    child instanceof Parent;
    
  6. 寄生组合继承
    使用 Object.create(Parent.prototype) 创建一个新的原型对象赋予子类从而解决组合继承的缺陷:

    // 寄生组合继承实现
    
    function Parent(value) {
    this.value = value;
    }
    
    Parent.prototype.getValue = function() {
    console.log(this.value);
    }
    
    function Child(value) {
    Parent.call(this, value)
    }
    
    Child.prototype = Object.create(Parent.prototype, {
    constructor: {
        value: Child,
        enumerable: false, // 不可枚举该属性
        writable: true, // 可改写该属性
        configurable: true // 可用 delete 删除该属性
    }
    })
    
    const child = new Child(1)
    child.getValue();
    child instanceof Parent;