# js原型 #tag prototype 原型 __proto__ ## 原型对象 #d 何为原型对象 原型对象是每个函数都有的属性, js中的函数会有一个`protorype`属性, 这个属性是一个对象, 这个对象有一个属性为`constructor`,意为构造函数. 专门指向函数本身. 在原型对象中, 存放着构造函数的公共属性和方法. #l(构造函数) #e 原型示意 构造函数 FN ```javascript 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 | ```javascript let fn = new FN() fn.say() // hello my name is p ``` #d 原型对象作用 1. 存放构造函数的公共属性和方法 2. 用于构建实例对象 3. 构造函数的实例对象, 都会共享原型对象中的属性和方法 ## 原型链 #d 什么是原型链 在js中, 每个对象都有一个`__proto__`的属性, 这个属性指向了该对象的[原型对象], 但是这个[原型对象]本身是一个对象, 所以它也有[原型]属性, 所以我们将之称为[原型链]. 这个链条的最终会指向`Object`对象, Object也会有一个[原型]属性, 但是这个属性为`null` #e 原型链示意 下面是一个简单的原型链示意图 ```javascript 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 原型链应用示例 ```javascript 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. 父类构造函数 ```javascript 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. 子类构造函数 ```javascript 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. 父类属性赋值 ```javascript Student.prototype.name = 's' s.say() // hello my name is s ``` 4. 另一种写法 该写法, 会将父类构造函数中的属性和方法, 赋值给子类构造函数的原型对象中, 其属性与方法, 都是子类实例对象所共享的, 不会影响父类构造函数中的属性和方法 ```javascript 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. 组合继承 父类的构造函数被调用了两次(创建子类原型时调用了一次,创建子类实例时又调用了一次), 导致子类原型上会存在父类实例属性,浪费内存。 ```javascript 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) 创建一个新的原型对象赋予子类从而解决组合继承的缺陷: ```javascript // 寄生组合继承实现 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; ```