原型和原型链
原型和原型链
只有函数才有 prototype属性,
只有对象才有 __proto__属性,
构造函数创建对象
function Person () {this.name = 'name'; // 每次都对实例对象自身做了一个扩展}let person = new Person(); // 创建一个空对象,{},并给这个对象的__proto__ 赋值,也就是构造函数的prototypeperson.name = 'hello world!';person.gender = 'man';console.log(person);
prototype
每个函数都有一个 prototype属性,如:
function Person () {this.age = 100;};// prototype是函数才会有的属性Person.prototype.type = 'person';Person.prototype.age = 0;let person = new Person();console.log(person.age); // 100
函数的prototype属性指向了一个对象,这个对象是调用该构造函数而创建实例的原型,也就是该实例person中的原型;
person.__proto__ === Person.prototype;
当我们创建了一个构造函数的时候,或者声明了一个class的时候,这个时候这个变量存在一个prototype对象,
当使用该构造函数去创建实例的时候,这个实例的原型就是这个对象。
构造函数和实例原型之间的关系:

proto
每个JavaScript对象都含有一个属性(隐式原型),叫做__proto__,该属性指向该对象的原型,
绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,
实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,
当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)。
function Person() {}var person = new Person();console.log(person.__proto__ === Person.prototype); // true
constructor
每个原型都有一个 constructor 属性指向关联的构造函数
function Person() {}console.log(Person === Person.prototype.constructor); // true
更新关系图如下:
综上:
function Person() {}var person = new Person();console.log(person.__proto__ == Person.prototype) // trueconsole.log(Person.prototype.constructor == Person) // true// 顺便学习一个ES5的方法,可以获得对象的原型console.log(Object.getPrototypeOf(person) === Person.prototype) // true
实例与原型
当读取实例的属性时,如果找不到,会从与之关联的原型中的去查找,如果还找不到,会去原型的原型去查找,一直找到 Object,如果还没有,就返回 undefined
因为到Object时,此时的 Object.prototype.__proto__ 的值为 null ,Object.prototype 没有原型,那么也就不存在任何属性。
console.log(Object.prototype.__proto__ === null) // true
原型的原型

原型链
Object.prototype 的原型
Object.prototype.__proto__ === null
null代表什么?
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
关系图更新:
由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
原型的缺点
单独使用原型模式的问题
原型中所有属性都是共享的,对于多个实例操作同一个引用类型,指向原型的同一个引用,最终导致原型上的数据发生改变,所有的实例都改变了。
let Person = function () {}Person.prototype = {gender: 'sex',friends: ['a', 'b', 'c']}let p1 = new Person();let p2 = new Person();p1.friends.push('d');console.log(p1.friends); // ['a', 'b', 'c', 'd']console.log(p2.friends); // ['a', 'b', 'c', 'd']
显然,这不是我们想要的;
可以使用构造函数+原型模式,
构造函数用于定义实例属性,
原型中用于定义共享属性。
let Person = function () {this.friends = ['a', 'b', 'c'];}Person.prototype = {gender: 'sex',}let p1 = new Person();let p2 = new Person();p1.friends.push('d');console.log(p1.friends); // ['a', 'b', 'c', 'd']console.log(p2.friends); // ['a', 'b', 'c',]
new
new Foo的过程
1、创建一个新对象,他的原型__proto__指向构造函数的prototype
2、执行构造函数,重新指定this为该新对象,并传入对应参数
3、返回一个对象,如果构造函数返回一个对象,则返回这个对象,否则返回创建的新对象
编码如下:
// 模拟new的实现let newMock = function () {let argus = Array.prototype.slice.apply(arguments);let Foo = argus.shift();let target = Object.create(Foo.prototype);let foo = Foo.apply(target, argus);if (typeof foo === 'object') {// 比如构造函数返回了一个Object,工厂模式之类的return foo;} else {return target;}}// 构造函数let Person = function (name) {this.name = `i am ${name}`;this.say = function () {console.log(this)}}// 使用let p = newMock(Person, 'www')console.log(p instanceof Person) // === true 说明newMock使用成功
继承
利用原型让一个引用类型继承另一个引用类型的属性和方法;
借助构造函数
function Person1 (name) {this.name = name;}function Stu1 (name, sno) {Person1.call(this, name)this.sno = sno;}let stu = new Stu1();stu instanceof Stu1; // true;stu instanceof Person1; // false;stu.constructor // Stu1
缺点:不能够继承父类的原型链
借助原型链
function Person2 (name) {this.name = name;this.colors = [1, 2, 3];}function Stu2 (sno) {this.sno = sno;}Stu2.prototype = new Person2();let stu = new Stu2('www', 1);let stu2 = new Stu2('222', 2);stu.colors.push(4);console.log(stu); // colors [1,2,3,4]console.log(stu2); // colors [1,2,3,4]console.log(stu instanceof Stu2); // true;console.log(stu instanceof Person2); // true;console.log(stu.constructor) // Person2
缺点:因为共享原型属性,有一个原型有修改另一个也跟着修改了
组合式继承
function Person3 (name) {this.name = name;}function Stu3 (name, sno) {Person3.call(this, name)this.sno = sno;}Stu3.prototype = new Person3();Stu3.prototype.constructor = Stu3; // 构造函数let stu = new Stu3();stu instanceof Stu3; // true;stu instanceof Person3; // true;stu.constructor // Stu3
缺点:父类的构造函数执行了两次
原型式继承
ES6新增Object.create方法,规范了原型式继承
function Person4 (name) {this.name = name;this.colors = [1, 2, 3];}function Stu4 (name, sno) {Person4.call(this, name)this.sno = sno;}// Stu4.prototype = new Person4(); // 父类的实例 --> 实例化了两次// Stu4.prototype = Person4.prototype; // 父类的原型 ---> 默认constructor是父类的constructorStu4.prototype = Object.create(Person4.prototype); // 原型式继承(隔离父类的原型)Stu4.prototype.constructor = Stu4; // 构造函数let stu = new Stu4('www', 1);let stu2 = new Stu4('222', 2);stu.colors.push(4);console.log(stu);console.log(stu2);console.log(stu instanceof Stu4); // true;console.log(stu instanceof Person4); // true;console.log(stu.constructor) // Stu4
