# 原型和应用
# 原型和原型链
# 原型的定义
原型是 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__去查找祖先的属性
# 需要特别注意的点
- 如果原型本来有值,后面又被直接赋给一个对象,则原有的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
- new 一个对象,其实就是将构造函数的原型赋给了这个对象,并且将 this 指向了这个对象
var person = new Person()
等价于
function Person(){
var this = {
___proto__: Person.prototype
}
}
这里的 this 和 person 就是一回事,可以这么理解,
以后想要找到实例对象就可以通过构造函数的this去获取,其实后续的原型链也是依此展开的
Son.prototype = new Father()
# 原型的应用
- 利用原型的特点和概念,可以提取公有属性
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;
// 这样公共属性就不用每次都去赋值,一次就好了
- 对象查找该对象的构造器是什么
function Car() {}
var car = new Car()
car.constructor == Car // true
console.log(car.constructor) // function Car(){}
因为刚开始构造函数的prototype里面就保存了constructor
Car.prototype={
constructor:func
}
car继承了祖先方法和属性,所以调用自然可以找到该构造器是什么
- Object.create() 参数必须是一个原型对象,创建一个有该原型对象的对象
var obj1 = { name: "surry" };
var obj = Object.create(obj1);
// obj.name = surry
# 原型链的定义
每个显式原型下也有一个隐式原型,指向规则和上面一样。在每一层首先寻找自身的属性或方法,如果没有通过隐式原型去找上一层的显示原型的方法,依次形成一个链
原型链的最高层Object
的显示原型拥有toString
和hasOwnProperty
方法和自身隐式原型指向 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;
}
- 只调用一次父类的构造函数,避免了在子类原型上创建不必要的,多余的属性
- 原型链保持不变