|
@@ -0,0 +1,211 @@
|
|
|
+# 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;
|
|
|
+```
|