原型与原型链相关面试题

309 阅读3分钟

原型相关的面试题

如何准确判断一个变量是不是数组

使用 变量 instanceof Array

手写一个简易的jQuery,考虑插件和扩展性

class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        const length = result.length
        for (let i = 0; i < length; i++) {
            this[i] = result[i]
        }
        this.length = length
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
    // 扩展很多 DOM API
}

// 插件
jQuery.prototype.dialog = function (info) {
    alert(info)
}

// “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {

    }
    style(data) {
        
    }
}

// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

class 的原型本质,怎么理解?

JavaScript 的 Class 概念其实是语法糖,JavaScript 仍是基于原型的,而 Java/C++ 等则是基于类的。

原型

每个 class 都有显式原型 prototype
每个实例都有隐式原型 __proto__
实例的 __proto__ 指向对应 class 的 prototype

虽然实例通过 __proto__ 获取原型,但规范最初是使用[[prototype]]去访问原型的。

原型链

基于原型的属性/方法查询规则

先在实例自身属性和方法中寻找,如果没有找到则在 实例的 __proto__ 中查找(原型链自自身向上查找) 

补充内容

Class

首先,认清 Class 是用作构造器/构造函数和操作符 new 的语法糖(即简化),类在 JavaScript 中是函数。

当 new User("John") 被调用:

一个新对象被创建。constructor 使用给定的参数运行,并为其分配 this.name。

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// 佐证:User 是一个函数
alert(typeof User); // function

class User {...} 构造实际上做了如下的事儿:

  1. 创建一个名为 User 的函数,该函数成为类声明的结果。该函数的代码来自于 constructor 方法(如果我们不编写这种方法,那么它就被假定为空)。

  2. 在 F.prototype 中存储类中的方法,例如 User.prototype 中的 sayHi。

class又不仅仅是语法糖:

  1. 首先,通过 class 创建的函数具有特殊的内部属性标记 [[FunctionKind]]:"classConstructor"。因此,它与手动创建并不完全相同。编程语言会在许多地方检查该属性。例如,与普通函数不同,必须使用 new 来调用它:
class User {
  constructor() {}
}

alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
  1. 此外,大多数 JavaScript 引擎中的类构造器的字符串表示形式都以 “class…” 开头
class User {
  constructor() {}
}

alert(User); // class User { ... }
  1. 类方法不可枚举。 类定义将 "prototype" 中的所有方法的 enumerable 标志设置为 false。这很好,因为如果我们对一个对象调用 for..in 方法,我们通常不希望 class 方法出现。

  2. 类总是使用 use strict。 在类构造中的所有代码都将自动进入严格模式。

Class 继承

扩展另一个类的语法是:class Child extends Parent

super() 语法,用于:

  1. 执行 super.method(...) 来调用一个父类方法。
  2. 执行 super(...) 来调用一个父类 constructor(只能在我们的 constructor 中)。继承类的 constructor 必须调用 super(...),并且 (!) 一定要在使用 this 之前调用。
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }

}

class Rabbit extends Animal {
  hide() {
    super();
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // 调用父类的 stop
    this.hide(); // 然后 hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.stop(); // White Rabbit 停止了。White rabbit hide 了!

如果一个类扩展了另一个类并且没有 constructor,那么将生成下面这样的“空” constructor:

class Rabbit extends Animal {
  // 为没有自己的 constructor 的扩展类生成的
  constructor(...args) {
    super(...args);
  }
}

本文使用 mdnice 排版