一、 问题

  1. 下面代码执行结果?(原型prototype)
  1. function Test() {}
  2. const t1 = new Test();
  3. console.log(t1.__proto__ === Test.prototype);
  4. console.log(Test.prototype.constructor === Test);
  5. console.log({}.constructor === Object);
  6. console.log(Test.prototype.__proto__ === Object.prototype);

根据原理中的图示,可知打印的结果都是true。

  1. 下面代码执行结果?(对象的私有属性和原型属性)
  1. function Test(name) {
  2. this.name = name;
  3. }
  4. Test.prototype = {
  5. func: 'Test'
  6. };
  7. const t1 = new Test('t1');
  8. const t2 = new Test('t2');
  9. console.log(t1.name);
  10. console.log(t2.name);
  11. console.log(t1.func);
  12. console.log(t2.func);
  13. console.log(t1.test);
  14. t1.name = 't11';
  15. console.log(t1.name);
  16. console.log(t2.name);
  17. t1.func = 'Test1';
  18. console.log(t1.func);
  19. console.log(t2.func);

答案:

  1. console.log(t1.name); // t1 私有属性
  2. console.log(t2.name); // t2 私有属性
  3. console.log(t1.func); // Test 原型属性
  4. console.log(t2.func); // Test 原型属性
  5. console.log(t1.test); // undefined 私有属性中没有,原型中也没有
  6. t1.name = 't11';
  7. console.log(t1.name); // t11 修改了私有属性
  8. console.log(t2.name); // t2 一个实例的属性修改不影响其他实例的私有属性
  9. t1.func = 'Test1';
  10. console.log(t1.func); // Test1 t1增加了私有属性,覆盖了原型属性
  11. console.log(t2.func); // Test t2访问的还是原型属性
  1. 下面代码执行结果?(原型链继承)
  1. function Parent() {
  2. this.name = 'parent';
  3. }
  4. function Child() {
  5. this.age = 11;
  6. }
  7. Child.prototype = new Parent();
  8. const c = new Child();
  9. console.log(c.age, c.name);

由原型继承的原理,可知打印结果是“11, parent”。age是私有属性,name是原型属性。

二、 概念和原理

面向对象

构造函数和对象

  1. js可以通过构造函数创建对象:
  1. function Test() {}
  2. const t = new Test();

Test被称为“类”或者“构造函数”。Test是t的构造函数。

也可以定义字面量对象

  1. const a = {};
  2. // 等价于
  3. const a = new Object();

a的构造函数是Object。

我们自己定义构造函数时候,可以给对象添加属性

  1. function Test(name) {
  2. this.name = name;
  3. }
  4. const t1 = new Test('t1');
  5. console.log(t1.name); // t1

new的原理

使用new操作符调用构造函数,做了下面这些工作:

  1. 创建了一个空的js对象(即{})
  2. 将空对象的原型prototype指向构造函数的原型
  3. 将空对象作为构造函数的上下文(改变this指向)
  4. 判断构造函数的返回值,以决定最终返回的结果。
    1. 如果返回值是基础数据类型,则忽略返回值;
    2. 如果返回值是引用数据类型,则使用return 的返回,也就是new操作符无效;

所以,任何一个构造函数的原型都是Object的实例。

  1. function Test() {}
  2. console.log(Test.prototype instanceof Object); // true

原型链

使用构造函数生成的对象,和构造函数之间有一些关系:

(1) 构造函数有个prototype对象(原型),该对象有个“constructor”属性,指向构造函数
(2) 每个对象都有一个“**proto”属性,指向它的构造函数的“prototype”属性
(3) 构造函数的prototype对象,也有一个“__
proto__**”对象,它指向Object的prototype对象
见图示

原型链和原型链继承 - 图1

当我们访问对象中的属性时候,会先访问该对象中的本身的属性(私有属性),如果访问不到,会查找对象的“**proto”指向的构造函数的prototype对象,如果其中有要访问的属性,就使用该值,否则继续访问prototype的“__proto__**”,在其中查找要访问属性。这样一直上溯到Object对象。这个就是“原型链”。
原型链提供了继承的能力。

继承

继承用来实现代码复用,即子类继承父类的属性和方法,就实现了父类代码的复用。

使用原型链如何实现继承呢?如果我们有一个父类Parent,Parent有一个方法,打印自己的name属性。

  1. function Parent() {
  2. this.showName = function () {
  3. console.log(this.name);
  4. }
  5. }

如果我们希望子类可以继承父类的方法,如何实现?通过前面介绍的原理,子类对象查找属性时候如果在自己私有属性中访问不到,会到它构造函数的prototype属性中查找,那么我们给子类构造函数的prototype赋值为父类对象,就可以让子类也可以访问父类的方法“showName”了。

  1. function Child() {
  2. this.name = 'child';
  3. }
  4. Child.prototype = new Parent();
  5. const c = new Child();
  6. c.showName();

当然使用原型链继承无法给父类传递参数,所以在实际应用中,需要结合其他继承方法。