原型和原型链

原型和原型链

只有函数才有 prototype属性,

只有对象才有 __proto__属性,

构造函数创建对象

  1. function Person () {
  2. this.name = 'name'; // 每次都对实例对象自身做了一个扩展
  3. }
  4. let person = new Person(); // 创建一个空对象,{},并给这个对象的__proto__ 赋值,也就是构造函数的prototype
  5. person.name = 'hello world!';
  6. person.gender = 'man';
  7. console.log(person);

prototype

每个函数都有一个 prototype属性,如:

  1. function Person () {
  2. this.age = 100;
  3. };
  4. // prototype是函数才会有的属性
  5. Person.prototype.type = 'person';
  6. Person.prototype.age = 0;
  7. let person = new Person();
  8. console.log(person.age); // 100

函数的prototype属性指向了一个对象,这个对象是调用该构造函数而创建实例的原型,也就是该实例person中的原型;

  1. person.__proto__ === Person.prototype;

当我们创建了一个构造函数的时候,或者声明了一个class的时候,这个时候这个变量存在一个prototype对象,
当使用该构造函数去创建实例的时候,这个实例的原型就是这个对象。

构造函数和实例原型之间的关系:

image.png

proto

每个JavaScript对象都含有一个属性(隐式原型),叫做__proto__,该属性指向该对象的原型,

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,
实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter
当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.__proto__ === Person.prototype); // true

image.png

constructor

每个原型都有一个 constructor 属性指向关联的构造函数

  1. function Person() {
  2. }
  3. console.log(Person === Person.prototype.constructor); // true

更新关系图如下:
image.png

综上:

  1. function Person() {
  2. }
  3. var person = new Person();
  4. console.log(person.__proto__ == Person.prototype) // true
  5. console.log(Person.prototype.constructor == Person) // true
  6. // 顺便学习一个ES5的方法,可以获得对象的原型
  7. console.log(Object.getPrototypeOf(person) === Person.prototype) // true

实例与原型

当读取实例的属性时,如果找不到,会从与之关联的原型中的去查找,如果还找不到,会去原型的原型去查找,一直找到 Object,如果还没有,就返回 undefined

因为到Object时,此时的 Object.prototype.__proto__ 的值为 nullObject.prototype 没有原型,那么也就不存在任何属性。

  1. console.log(Object.prototype.__proto__ === null) // true

原型的原型

image.png

原型链

Object.prototype 的原型

  1. Object.prototype.__proto__ === null

null代表什么?

  1. null 表示“没有对象”,即该处不应该有值。

所以 Object.prototype.__proto__ 的值为 nullObject.prototype 没有原型,其实表达了一个意思。

所以查找属性的时候查到 Object.prototype 就可以停止查找了。

关系图更新:
image.png

由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

原型的缺点

单独使用原型模式的问题

原型中所有属性都是共享的,对于多个实例操作同一个引用类型,指向原型的同一个引用,最终导致原型上的数据发生改变,所有的实例都改变了。

  1. let Person = function () {
  2. }
  3. Person.prototype = {
  4. gender: 'sex',
  5. friends: ['a', 'b', 'c']
  6. }
  7. let p1 = new Person();
  8. let p2 = new Person();
  9. p1.friends.push('d');
  10. console.log(p1.friends); // ['a', 'b', 'c', 'd']
  11. console.log(p2.friends); // ['a', 'b', 'c', 'd']

显然,这不是我们想要的;

可以使用构造函数+原型模式,
构造函数用于定义实例属性,
原型中用于定义共享属性。

  1. let Person = function () {
  2. this.friends = ['a', 'b', 'c'];
  3. }
  4. Person.prototype = {
  5. gender: 'sex',
  6. }
  7. let p1 = new Person();
  8. let p2 = new Person();
  9. p1.friends.push('d');
  10. console.log(p1.friends); // ['a', 'b', 'c', 'd']
  11. console.log(p2.friends); // ['a', 'b', 'c',]

new

new Foo的过程
1、创建一个新对象,他的原型__proto__指向构造函数的prototype
2、执行构造函数,重新指定this为该新对象,并传入对应参数
3、返回一个对象,如果构造函数返回一个对象,则返回这个对象,否则返回创建的新对象

编码如下:

  1. // 模拟new的实现
  2. let newMock = function () {
  3. let argus = Array.prototype.slice.apply(arguments);
  4. let Foo = argus.shift();
  5. let target = Object.create(Foo.prototype);
  6. let foo = Foo.apply(target, argus);
  7. if (typeof foo === 'object') {
  8. // 比如构造函数返回了一个Object,工厂模式之类的
  9. return foo;
  10. } else {
  11. return target;
  12. }
  13. }
  14. // 构造函数
  15. let Person = function (name) {
  16. this.name = `i am ${name}`;
  17. this.say = function () {
  18. console.log(this)
  19. }
  20. }
  21. // 使用
  22. let p = newMock(Person, 'www')
  23. console.log(p instanceof Person) // === true 说明newMock使用成功

继承

利用原型让一个引用类型继承另一个引用类型的属性和方法;

借助构造函数

  1. function Person1 (name) {
  2. this.name = name;
  3. }
  4. function Stu1 (name, sno) {
  5. Person1.call(this, name)
  6. this.sno = sno;
  7. }
  8. let stu = new Stu1();
  9. stu instanceof Stu1; // true;
  10. stu instanceof Person1; // false;
  11. stu.constructor // Stu1

缺点:不能够继承父类的原型链

借助原型链

  1. function Person2 (name) {
  2. this.name = name;
  3. this.colors = [1, 2, 3];
  4. }
  5. function Stu2 (sno) {
  6. this.sno = sno;
  7. }
  8. Stu2.prototype = new Person2();
  9. let stu = new Stu2('www', 1);
  10. let stu2 = new Stu2('222', 2);
  11. stu.colors.push(4);
  12. console.log(stu); // colors [1,2,3,4]
  13. console.log(stu2); // colors [1,2,3,4]
  14. console.log(stu instanceof Stu2); // true;
  15. console.log(stu instanceof Person2); // true;
  16. console.log(stu.constructor) // Person2

缺点:因为共享原型属性,有一个原型有修改另一个也跟着修改了

组合式继承

  1. function Person3 (name) {
  2. this.name = name;
  3. }
  4. function Stu3 (name, sno) {
  5. Person3.call(this, name)
  6. this.sno = sno;
  7. }
  8. Stu3.prototype = new Person3();
  9. Stu3.prototype.constructor = Stu3; // 构造函数
  10. let stu = new Stu3();
  11. stu instanceof Stu3; // true;
  12. stu instanceof Person3; // true;
  13. stu.constructor // Stu3

缺点:父类的构造函数执行了两次

原型式继承

ES6新增Object.create方法,规范了原型式继承

  1. function Person4 (name) {
  2. this.name = name;
  3. this.colors = [1, 2, 3];
  4. }
  5. function Stu4 (name, sno) {
  6. Person4.call(this, name)
  7. this.sno = sno;
  8. }
  9. // Stu4.prototype = new Person4(); // 父类的实例 --> 实例化了两次
  10. // Stu4.prototype = Person4.prototype; // 父类的原型 ---> 默认constructor是父类的constructor
  11. Stu4.prototype = Object.create(Person4.prototype); // 原型式继承(隔离父类的原型)
  12. Stu4.prototype.constructor = Stu4; // 构造函数
  13. let stu = new Stu4('www', 1);
  14. let stu2 = new Stu4('222', 2);
  15. stu.colors.push(4);
  16. console.log(stu);
  17. console.log(stu2);
  18. console.log(stu instanceof Stu4); // true;
  19. console.log(stu instanceof Person4); // true;
  20. console.log(stu.constructor) // Stu4