# 原型和应用

# 原型和原型链

# 原型的定义

原型是 function 对象的一个属性,它定义了构造函数制造出来的对象的公有祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法,原型其实就是一个对象。

可能第一眼看定义是懵的,别急,逐行解释

首先定义一个构造函数(大写驼峰式) Person
function Person(){

}

然后通过构造函数制造一个对象

var person = new Person()

然后原型怎么写呢
Person.prototype = {} // 天生就有

按照定义: 他定义了构造函数只找出来的对象的公有祖先,也就是说这个 `prototype` 对象是 person的祖先,
并且能够使用里面的属性和方法

Person.prototype.name = "deng"
console.log(person.name) // deng
虽然 person对象上没有该属性,但是祖先有,拿过来用,这就是全部过程

# 如何理解proto

Person.prototype.name = "abc";
function Person() {}
var obj = {
  name: "sunny"
};
var person = new Person();
person.__proto__ = obj; // person继承的对象就改成了obj了
console.log(person.name); //sunny
1.首先理解这个问题需要清楚person是怎么知道除了自身属性没有
然后去查找祖先Person的原型Prototype的:

  当使用new操作符时,其实就是在构造函数内部定义了一个this,
  然后this下面有很多属性,其中有一个__proto__隐式原型属性,
  它的值就是指向Person的原型也就是Person.prototype。
  这也是person怎么查找祖先的根本

  var person = new Person()

  等价于

  function Person(){
    var this = {
      ___proto__: Person.prototype
    }
  }

  这也是person如何修改Person的原型的唯一途径

2. person查找变量,首先找自己身上的,没有就通过__proto__去查找祖先的属性

# 需要特别注意的点

  1. 如果原型本来有值,后面又被直接赋给一个对象,则原有的proto指向的原型是没变的,改变的只是 Person.prototype
Person.prototype.name = 'surry'
function Person(){

}
var person = new Person()
Person.prototype = {
  name:'cherry'
}
console.log(person.name) //surry
。。。。。。。。。。。。。。分割线
Person.prototype.name = 'surry'
function Person(){

}
Person.prototype = {
  name:'cherry'
}
var person = new Person()

console.log(person.name) //注意预编译 函数被提上去了 cherry

  1. new 一个对象,其实就是将构造函数的原型赋给了这个对象,并且将 this 指向了这个对象
 var person = new Person()

  等价于

  function Person(){
    var this = {
      ___proto__: Person.prototype
    }
  }

  这里的 this 和 person 就是一回事,可以这么理解,
  以后想要找到实例对象就可以通过构造函数的this去获取,其实后续的原型链也是依此展开的

  Son.prototype = new Father()

# 原型的应用

  1. 利用原型的特点和概念,可以提取公有属性
function Person(name, age) {
  this.sex = "male";
  this.level = 2;
  this.name = name;
  this.age = age;
}

var person1 = new Person(); // 想要person上有sex,level,每次都会执行两条语句,
//造成了代码的冗余,利用原型提取公共属性

Person.prototype.sex = "male";
Person.prototype.level = 2;
// 这样公共属性就不用每次都去赋值,一次就好了
  1. 对象查找该对象的构造器是什么
function Car() {}

var car = new Car()

car.constructor == Car // true

console.log(car.constructor) // function Car(){}

因为刚开始构造函数的prototype里面就保存了constructor

Car.prototype={
  constructor:func
}

car继承了祖先方法和属性,所以调用自然可以找到该构造器是什么
  1. Object.create() 参数必须是一个原型对象,创建一个有该原型对象的对象
var obj1 = { name: "surry" };
var obj = Object.create(obj1);

// obj.name = surry

# 原型链的定义

每个显式原型下也有一个隐式原型,指向规则和上面一样。在每一层首先寻找自身的属性或方法,如果没有通过隐式原型去找上一层的显示原型的方法,依次形成一个链

原型链的最高层Object的显示原型拥有toStringhasOwnProperty方法和自身隐式原型指向 null

# 模拟 new 运算符

了解了 new Person 的过程,实现一个 new 其实很简单。传入的参数有构造函数和参数数组

1. 将构造函数的原型赋值给新建的obj的隐式原型__proto__。
2. 在obj下执行构造函数,并传入参数,
   这个时候构造函数内的this就是obj。
3. 如果这个'构造函数'没有return对象格式的结果,
   返回新创建的obj。

function Person(){
// 伪代码
var this = {

}
}
var person = new Person() // 这一步等价于

person.__proto__ = Person.prototype
Person.this = person // Person里面的this就是person 这俩一回事

所以手写 new 的代码就可以写成

function MyNew = function(Ctor,...args){
  const obj = Object.create(Ctor.prototype)
  const ret = Ctor.apply(obj,args)
  return ret instanceof Object ? ret : obj
}

# Object.create

const myCreate = function(obj) {
  function F() {}
  F.prototype = obj;
  return new F(); // 创建一个继承 obj 原型属性的纯净对象
};

# JS 原型链和实现继承的各种方式和优缺点

# 原型链继承

此类继承方式虽然简单,但是会继承很多多余的属性

Grand.prototype.lastName = "deng";
function Grand() {}
var grand = new Grand();
Father.prototype = grand;
function Father() {
  this.name = "hiehie";
}
var father = new Father();
Son.prototype = father;
function Son() {}
var son = new Son();

# 构造函数继承

这种调用父级构造函数逇方式其实和上面那个一样,虽然写法好看了,但是无法借用构造函数的原型

function Person(name, age) {
  this.age = age;
  this.name = name;
}
function Student(name, age, grad) {
  Person.call(this, name, age);
  this.grad = grad;
}

var student = new Student("1", 2, 2);

# 公有原型继承

这种方式缺点是无法新增自己的原型属性,会互相影响

Father.prototype.lastName = "李雷";
function Father() {}
function Son() {}
// 两个构造函数继承同一个原型
Son.prototype = Father.prototype;
var son = new Son();
var father = new Father();
Son.prototype.name = "lihua"; //这样会导致Father.prototype = "lihua"

# 圣杯模式继承

使用了一个中间函数做过渡,弥补前两种方式的缺点,解决了公有原型无法增加私有原型属性的缺点

function inherit(Target, Origin) {
  function F() {}
  F.prototype = Origin.prototype;
  // 通过原型链继承  只不过经过了中间层F
  Target.prototype = new F();
  // son.__proto ===> newF().__proto__===>Father.prototype
  // 这一步是因为原型链将son的__proto指向了Father的prototype 所以要指回去
  Target.prototype.constuctor = Target;
}

# 寄生组合继承

function inheritPrototype(Sub, Super) {
  // Object.create 可以将subPrototype的原型(__proto__) 设置成 Super的原型对象(prototype);
  var subPrototype = Object.create(Super.prototype);
  // 因为Object.prototype.constructor会返回对象本身,所以这里设置为Sub本身;
  subPrototype.constructor = Sub;

  // 这里将Sub的原型对象(prototype)设置成subPrototype,实现Sub对Super基于原型链的继承;
  Sub.prototype = subPrototype;
}
  1. 只调用一次父类的构造函数,避免了在子类原型上创建不必要的,多余的属性
  2. 原型链保持不变
Last update: 7/20/2021, 8:53:54 AM