原型链的终点, 类的继承

从对 js 原型和原型链查找的特性, 我们知道了两个最为重要的结论:

  • 函数对象天生存在 prototype 属性, 它是一个对象, 而它的 constructor 属性指回 函数自身
  • 构造函数的 prototype 属性是 实例对象的原型 , 即 youge.__ proto __ == People.prototype
  • JS 规定实例可以打点访问原型上的属性和方法, 类似类变量或方法, 即称为 原型链查找

原型链的终点

如上例, 构造函数的的原型 People.prototype 并不是原型链的终点, 它往上还有原型是 Object.prototype

那说明与之对应的还有一个 Object 对象, 即为原型链的终点, 简单演示这段关系就是:

// 类似有 2个构造函数, 一个实例:
Object;
People;
youge;

// 他们端原型链关系是: 
youge.__proto__ = People.prototype
People.prototype.__proto__ = Object.prototype

// 最顶端是 Object 
Object.prototype 天生存在

还是代码演示吧:

function People(name, age, sex) {
  this.name = name 
  this.age = age 
  this.sex = sex 
}

// 实例化
var youge = new People('油哥', 18, '男')


// 构造函数的原型, 是 Object.prototype
console.log(youge.__proto__ === People.prototype);
console.log(People.prototype.__proto__ == Object.prototype);

// 简写就是
console.log(youge.__proto__.__proto__ == Object.prototype);

// 终点就是 Object, 再往上就是 null 啦
console.log(youge.__proto__.__proto__.__proto__ == null);
true
true
true
true

内置的 Object 是所有对象的构造函数, 构造函数的 prototype 是对象, 那它的原型就是 Object . 从原型链的这个关系, 也就解释了为什么一个实例对象能调用类似 hasOwnProperty() 这样的方法, 因为它已经在 Object.prototype 中定义啦. 在调用时如果发现没有, 会沿着原型链往上查找哦, 如果找了终点都没有找到才最终返回 undefined.

不得不说, 这个 JS 创始人的这个独特的原型设计的精妙, 期初觉得很奇怪, 为啥不直接用类, 因为之前接触的语言大多都是面向对象的那一套东西嘛, 现在用了几年突然回首, 呀这个原型设计真的是巧妙至极呀 !

数组的原型链

在 js 中, 数组其实也是对象, 只不过键为索引而已, 它能让我们更加聚焦在值的观测上.

但注意的是, 所有数组的原型是 Array , 则数组的原型三角关系是:

Array -> Array.prototype -> arr.__proto__

Array 的原型也是 Object.prototype .

var arr = []

console.log(arr.__proto__ == Array.prototype); // true
// 数组原型链
console.log(arr.__proto__.__proto__ == Object.prototype);  // true

console.log(Array.prototype.hasOwnProperty('push')); // true
console.log(Array.prototype.hasOwnProperty('splice')); // true

console.log(Array.__proto__ == Object.prototype); // false
console.log(Array.__proto__ == Function.prototype); // true

通过原型链实现继承

面向对象三大特性, 封装, 继承, 多态. 那通过原型链呢我们可以实现 继承 的效果.

// 父类
function People(name, age, sex) {
  this.name = name 
  this.age = age 
  this.sex = sex 
}

People.prototype.sayHello = function () {
  console.log('hi, 我是: ', this.name + '今年: ', this.age, ' 岁啦!');
}

People.prototype.eat = function () {
  console.log(this.name, "开始吃肉肉.");
}

// 子类
function Student(name, age, sex, school, studentNumber) {
  // ES6 可以用 super
  this.name = name
  this.age = age
  this.sex = sex
  this.school = school
  this.studentNumber = studentNumber
}

// 关键语句实现继承
Student.prototype = new People()

Student.prototype.study = function () {
  console.log(this.name, ' 正在学习哦!');
}

Student.prototype.exam = function () {
  console.log(this.name, ' 正在考试哦!');
}


// 实例话
var youge = new Student('youge', 18, '男', '幼儿园', 123456)

youge.study()

// 能调用父类方法哦, 因为 Student.prototype == new People()
// 子类也能重写父类的 sayHello 方法
youge.sayHello()
youge  正在学习哦!
hi, 我是:  youge 今年:  18 岁啦!

发现这个案例, 实现继承的关键一句话是: Student.prototype = new People()

面向对象就简单介绍到这里啦. 总结下来就是, 面向对象的特点是事先对场景进行抽象类, 通过定义不同的类, 再通过类实例话进行程序交互. 它的有点就是可以让程序编写更加清洗, 代码结构更加严密, 使代码更加健壮和方便维护.

但是呢,

我写了一段时间 Java 代码后, 就有点烦这个面向对象了, 写得是真的难受, 感觉还是脚本写起来爽一点呀.

耐心和恒心, 总会获得回报的.