JS是一门面向对象的编程语言,JS 中的面向对象,围绕原型和原型链知识展开。
原型是 JavaScript 面向对象系统实现的根基。 javascript中创建对象不是基于‘类的’,而是通过构造函数和原型链实现的。
JS是基于原型链继承的。
在ES6中引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。ES6 中引入的 JavaScript 类实质上是 JavaScript 现有的基于原型的继承的语法糖。
1.构造函数
function Student(name,number) {
this.name = name
this.number = number
}
var xialuo = new Student("夏洛",23)
new 构造函数背后做的事有:
- 创建一个空对象,把this指向空对象;
- 将这个对象(this)的隐式原型(proto)指向构造函数.prototype(构造函数的原型对象);
- 执行构造函数.apply(this);
- return 这个对象。
面试题
构造函数中返回一个对象对结果有什么影响
function Super (a) {
this.a = a;
return 123;
}
Super.prototype.sayHello = function() {
alert('hello world');
}
function Super_ (a) {
this.a = a;
return {a: 2};
}
Super_.prototype.sayHello = function() {
alert('hello world');
}
const super1 = new Super(3)
const super2 = new Super_(3)
console.log(super1) // Super { a: 3 }
console.log(super2) // { a: 2 }
在构造函数中 return 基本类型 不会影响构造函数的值, 而 return 对象类型 则会替代构造函数返回该对象。
function Foo () {
getName = function () {
console.log(1)
return this
}
}
Foo.prototype.getName = function () {
console.log(3)
}
Foo.getName = function () {
console.log(2)
}
var getName = function () {
console.log(4)
}
function getName () {
console.log(5)
}
getName() // 4
Foo.getName() // 2
new Foo().getName() // 3
// Foo().getName // Cannot read property 'getName' of undefined
2. 理解原型和原型链
2.1 原型
每个构造函数都拥有一个 prototype 属性,它指向构造函数的原型对象,这个原型对象中有一个 construtor 属性指回构造函数;每个实例对象都有一个__proto__属性,当我们使用构造函数去创建实例时,实例的__proto__属性就会指向对应构造函数的原型对象。
function Student(name,number) {
this.name = name
this.number = number
}
Student.prototype.sayHi = function () {
console.log(`姓名:${this.name},学号:${this.number}`)
}
var xialuo = new Student("夏洛",23)
console.log(xialuo.name) //夏洛
console.log(xialuo.number) //23
xialuo.sayHi(); //姓名:夏洛,学号:23
原型图:
xialuo是通过Student new出来的,所以xialuo的__proto__指向Student的prototype
xialuo.proto === Student.prototype
查找规则:
获取实例属性xialuo.name或执行实例方法xialuo.sayHi()时,先在自身属性和方法寻找,找不到则自动通过实例的__proto__到对应class的prototype(Student.prototype)中查找。
ES6引入class写法:
class Student {
constructor(name, number) {
this.name = name
this.number = number
}
sayHi() {
console.log(`姓名:${this.name},学号:${this.number}`)
}
}
const xialuo = new Student("夏洛",23)
console.log(xialuo.name) //夏洛
console.log(xialuo.number) //23
xialuo.sayHi(); //姓名:夏洛,学号:23
xialuo.hasOwnProperty("name") //true
xialuo.hasOwnProperty("sayHi") //false
2.2 原型链
每个由构造器创建的对象,都有一个隐式引用(proto)链接到构造器的“prototype”属性值,即构造器的原型对象。原型对象可能有一个非空 (non-null) 隐式引用链接到它自己的原型,以此类推,这叫做 原型链 。
当试图访问一个 JavaScript 实例的属性 / 方法时,它首先搜索这个实例本身;当发现实例没有定义对应的属性 / 方法时,它会转而去搜索实例的原型对象;如果原型对象中也搜索不到,它就去搜索原型对象的原型对象,这个搜索的轨迹,就叫做原型链。
function People(name) {
this.name = name;
}
People.prototype.eat = function () {
console.log(`${this.name} eat something`)
}
//ES5寄生组合式实现继承
function Student(name,number) {
// 先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
People.call(this,name) // 借用构造函数, 第一次调用父类构造函数
this.number = number
}
Student.prototype = Object.create(People.prototype) // 使用 Object.create 内置函数,通过明确指定原型来创建一个新对象。
Student.prototype.constructor = Student;
Student.prototype.sayHi = function () {
console.log(`姓名:${this.name},学号:${this.number}`)
}
const xialuo = new Student("夏洛",23)
console.log(xialuo.name) //夏洛
console.log(xialuo.number) //23
xialuo.sayHi(); //姓名:夏洛,学号:23
原型链图:
xialuo是通过Student new出来的,所以xialuo的__proto__指向Student的prototype。Student继承于People,内部原理为Student.prototype的原型指向People.prototype,所以Student.prototype.__proto__指向People.prototype
ES6的继承写法:
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的属性添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。所以,在ES6中使用class实现继承,子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
// 该类通过extends关键字,继承了 People 类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个 People 类。
class Student extends People {
constructor (name,number) {
super(name) // 调用父类的constructor(name)
// super作为函数调用时,代表父类的构造函数。
// super()在这里相当于People.prototype.constructor.call(this)
this.number = number
}
sayHi() {
console.log(super.eat() + ',' + this.number) // super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。// ES6规定通过super调用父类的方法时,super会绑定子类的this。
}
}
const xialuo = new Student("夏洛",23)
console.log(xialuo.name) //夏洛
console.log(xialuo.number) //23
xialuo.sayHi(); //姓名:夏洛,学号:23
2. 判断继承关系的方法
- A instanceof B的原理:通过A.__proto__的指向在A的原型链中向上寻找,看是否能找到B.prototype。这种方法B必须是个构造函数;
console.log(xialuo instanceof Student) //true
console.log(xialuo instanceof People) //true
console.log(xialuo instanceof Object) //true
let arr = [1,2,3]
console.log(arr instanceof Array) //true
- B.prototype isPrototypeOf(A):在A的原型链中向上寻找,看是否能找到B.prototype,这种方法不必访问构造函数;
- Object.getPrototypeOf(A) === B.prototype ;
Object.getPrototypeOf(A):获取a的原型链,看是否能找到Foo.prototype; - A._proto _=== B.prototype。
3.属性屏蔽
如果属性名foo即出现在实例对象A中,也出现在A的原型链中,那么会发生屏蔽。
实例对象A中的foo属性会屏蔽原型链上层的所有foo属性,因为A.foo总是会选择原型链中最底层的foo属性。