大厂面经--js基础篇(原型,原型链,继承)

1、js中的原型和原型链

和其他的面向对象编程语言不同,最开始js并没有引入class的概念,但是js中有在大量使用对象,为了保证对象之间的联系,JavaScript引入了原型与原型链的概念。

1.1、什么是原型

在js中,每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。原型对象默认拥有一个constructor属性,指向指向它的那个构造函数,每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象。原型对象就是用来存放实例中共有的那部分属性。

1.2. 什么是原型链

js中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链, 所有原型链的终点都是Object函数的prototype属性.

1.3. 常见的题目

Array.__proto__ ===  Function.prototype  // true
Array.__proto__.__proto__ === Object.prototype  // true
Function.prototype.__proto__ === Object.prototype  // true


function F(){}
Function.prototype.a = function(){
	console.log('a1')
}
Function.prototype.b = function(){
	console.log('b1')
}
Object.prototype.a = function(){
	console.log('a2')
}
Object.prototype.b = function(){
	console.log('b2')
}
let f = new F()
f.a()
f.b()
F.a()
F.b()

2、js中的继承

js主要存在6种继承方式。

2.1 原型链继承

function Parent(){
    this.parent = [1,2,3]
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(child){
    this.child = child
}

Child.prototype = new Parent()
Child.prototype.constructor = Child

优点:

  • 这种方式的优点很明显,多个实例可以共享原型链上定义的属性和方法。

缺点:

  • 每个实例对引用类型的属性的修改也会被其他实例共享,这不是我们想看到的
  • 创建child的时候无法像构造函数传参,child实例无法初始化父类属性

2.1 构造函数继承

function Parent(parent){
    this.parent = parent
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(name,child){
    Parent.call(this,name)
    this.child = child
}

优点:

  • 克服了原型链继承带来的2个缺点

缺点:

  • 子类无法继承父类原型链上的方法;
  • 每次生成子类实例都会执行一次父函数

2.2 组合继承(常用的一种方式)

function Parent(parent){
    this.parent = parent
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(name,child){
    Parent.call(this,name)
    this.child = child
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

优点:解决了上面两种方法的缺点

  • 子类的构造函数会代替原型上的那个父类构造函数
  • 每个新实例引入的构造函数属性是私有的

缺点:

  • 父类的构造函数执行两次Parent.call(this,name)/new Parent()
  • 子类的构造函数会代替原型上的那个父类构造函数

2.4 寄生组合继承(常用)

function Parent(parent){
    this.parent = parent
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(name,child){
    Parent.call(this,name)
    this.child = child
}

function inheritPrototype(Parent, Child){
     //创建父类原型的一个副本,把副本赋值给子类原型,而不是new Parent(),减少一次父构造函数的调用
    Child.prototype = Object.create(Parent.prototype)
	Child.prototype.constructor = Child
}

解决了组合继承中父类构造函数执行两次的问题

继承扩展的知识点

1. 是Object.create的原理
Object.create => function(obj){
      var f = function(){};
      f.prototype = obj;
      return new f();
}

先在内部创建一个空构造函数
把构造函数的原型指向传进来的obj对象
通过new创建对象并返回

2. Object.create,new Object(), {}三个的区别(百度)

Object.create是使用指定的原型proto对象及其属性properties去创建一个新的对象,也就是我们新建的对象的原型对象是传入create的对象。
值得注意的是当create的参数为null的时候创建的新对象完全是一个空对象,没有原型,也就是没有继承Object.prototype上的方法。

new Object()和{}本质上没有区别,它们创建的新对象的原型指向的是 Object.prototype。

new Object()和{}在初始化的过程上有区别,前者是用构造函数实例化对象,后者是直接创建JSON对象,后者的初始化比较方便,可以在初始化的时候同时赋值。而两种方法创建的对象在使用上都是一样的,所以使用的时候都建议用后者,没有new的过程比较高效。

3. new的原理
function myNew () {
    //创建一个实例对象
    let obj = new Object()
    //传入构造函数
    let fn = Array.prototype.shift.call(arguments)
    let args = Array.prototype.slice.call(arguments,1)
    //实现继承
    obj.__proto__ = fn.prototype
    //调用构造器,改变this指向到实例
    let result = fn.apply(obj,args)
    return typeof result === 'object' ? result : obj
}
  • 创建一个新对象
  • 将构造函数的作用域赋给新对象
  • 调用构造器,改变this指向到实例,执行构造函数中的代码(为这个新对象添加属性)
  • 返回新对象。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章