前言
题目来自ConardLi的blog
写的是自己的题解,水平有限,所以仅供参考
代码会整合在github,觉得有帮助就给个star吧~
正文
一、Javascript基础
原型和原型链
1、理解原型设计模式以及JavaScript中的原型规则
每创建一个函数,函数上都会有一个prototype属性,访问一个对象的属性时,如果对象内不存在这个属性,那么就会访问__proto__
属性,如果还是找不到,就再继访问__proto__
属性继续找,直到null为止。
2、instanceof的底层实现原理,手动实现一个instanceof
const instanceOf = function (target, origin) {
const proto = target.__proto__
if (proto) {
if (proto === origin.prototype) {
return true
}else{
return instanceOf(proto,origin)
}
}else{
return false
}
}
3、实现继承的几种方式以及他们的优缺点
- 原型链继承
prototype
// 依靠原型链的继承
function Father() {
this.name = 'father'
}
Father.prototype.getFatherName = function () {
console.log(this.sayName)
}
function Child() {
this.name = 'child'
}
Child.prototype = new Father()
Child.prototype.getChildName = function(){
console.log(this.name)
}
const instance = new Child()
缺点:包含引用类型的原型属性会被所有的实例共享
function SuperType(){
this.colors = ['red', 'blue']
}
function SubType(){
}
SubType.prototype = new SuperType()
var instance1 = new SubType()
var instance2 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) //["red", "blue", "black"]
console.log(instance2.colors) //["red", "blue", "black"]
- 经典继承
this
为了解决上面引用类型的原型属性共享的问题,我们借用构造函数,在子函数的构造函数里面调用父元素的构造函数
借用构造函数的问题:
方法都在构造函数里面定义,函数复用无从谈起
function Father(){
this.color = ['blue','red']
}
function Child(){
Father.call(this)
}
const instance1 = new Father()
const instance2 = new Child()
instance1.color.push('yellow')
instance2.color.push('black')
console.log(instance1.color)
console.log(instance2.color)
输出:
- 组合继承
this+prototype
function SuperType(name) {
this.name = name
this.colors = ['red','blue','yellow']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name,age) {
SuperType.call(this,name)
this.age = age
}
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function() {
console.log(this.age)
}
var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
console.log(instance1.colors) // ["red", "blue", "yellow", "black"]
instance1.sayName() //Nicholas
instance1.sayAge() //29
var instance2 = new SubType('Greg', 27)
console.log(instance2.colors) //["red", "blue", "yellow"]
instance2.sayName() //Greg
instance2.sayAge() //27
- 原型式继承
这种继承模式封装了原型链继承,是原型链继承的升级版,原型链继承+工厂模式。
基于已有的对象创建新对象,同时还不必因此创建自定义类型,让继承的逻辑更好地分离出来。
首先我们得创建一个object函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
缺点:会调用两次父类构造函数,第一次是创建子类型原型的时候
也就是ES5的Object.create()简陋版的原理
const object = function(o){
function F(){}
F.prototype = o
return new F()
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')
const yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')
console.log(person.friends)
- 寄生式继承
增强原型式继承的功能的一种模式
缺点:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
const createAnother = function(original){
const clone = object(original)
clone.sayHi = function(){
console.log('hi')
}
return clone
}
const person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = createAnother(person)
anotherPerson.sayHi() //hi
- 寄生组合式继承
所谓寄生组合式继承,就是借用构造函数来继承属性
const object = function(o){
function F(){}
F.prototype = o
return new F()
}
const inheritPrototype = function(Child, Parent){
const prototype = object(Parent.prototype)
prototype.constructor = Child
Child.prototype = prototype
Child.__proto__ = Parent
}
function Parent(name) {
this.name = name
}
Parent.sayHello = function (){
console.log('hello')
}
Parent.prototype.sayName = function() {
console.log('my name is ' + this.name)
return this.name
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
inheritPrototype(Child, Parent)
Child.prototype.sayAge = function () {
console.log('my age is ' + this.age)
return this.age
}
5、至少说出一种开源项目(如Node)中应用原型继承的案例
jQuery
6、可以描述new一个对象的详细过程,手动实现一个new操作符
把大象放进冰箱有三步:
1、通过Object.create绑定原型并返回一个对象
2、绑定this
3、原构造函数如果返回值为对象,那么这个返回值会被正常使用,否则返回新对象。
提问:
-
Object.create返回的数据结构是怎么样的?
一个新对象,带着指定的原型对象和属性。
7、理解es6 class构造以及继承的底层实现原理
class Person(){
constructor(){
}
}
class构造:
- constructor就是原来构造函数的写法,就算不定义,也会有一个空的constructor
class中super使用场景:
super关键字用于访问和调用一个对象的父对象上的函数。
- 第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
并且必须在使用this关键字之前使用。
class A {}
class B extends A {
constructor() {
super()
}
}
- 调用父类上的静态方法
class Rectangle {
constructor() {}
static logNbSides() {
return 'I have 4 sides';
}
}
class Square extends Rectangle {
constructor() {}
static logDescription() {
return super.logNbSides() + ' which are all equal';
}
}
Square.logDescription(); // 'I have 4 sides which are all equal'
class底层实现原理:
const inherit = function (Child, Father) {
Child.prototype = Object.create(Father.prototype, {
constructor: {
enumerable: false,
configurable: true,
writable: true,
value: Father
}
})
Object.setPrototypeOf(Child,Father)
}
1、ES6 的 class 内部是基于寄生组合式继承,它是目前最理想的继承方式,通过 Object.create 方法创造一个空对象,并将这个空对象继承 Object.create 方法的参数,再让子类(subType)的原型对象等于这个空对象,就可以实现子类实例的原型等于这个空对象,而这个空对象的原型又等于父类原型对象(superType.prototype)的继承关系。
2、Object.create 支持第二个参数,即给生成的空对象定义属性和属性描述符/访问器描述符,我们可以给这个空对象定义一个 constructor 属性更加符合默认的继承行为,同时它是不可枚举的内部属性。(enumerable:false)
3、ES6 的 class 允许子类继承父类的静态方法和静态属性,而普通的寄生组合式继承只能做到实例与实例之间的继承,对于类与类之间的继承需要额外定义方法,这里使用 Object.setPrototypeOf() 将 superType 设置为 subType 的原型,从而能够从父类中继承静态方法和静态属性。