真正的王者级JavaScript知识点是怎么讲解的?

372 阅读6分钟

前言

不知道大家有没有这样的感觉,当我们系统的学习了一遍JavaScript后,想要去针对性的提高JavaScript能力时,会显得没有方向感。

并不是因为自己的能力不够,而是有很多内容分布的比较散列,我们需要花费很多精力去整理出来。

假如我们正在准备面试了,这种感受可能会更加明显,作为三大基础中最重要的JavaScript我该准备哪些内容呢?

各位看官别急,我这就给你稍微整理一下。

当我们有了这张脑图后,你以为光看看知识点,就可以轻松提升JavaScript技能了么?

当然不行,在具备理论知识后,我们还需要有与实际结合的能力,通过代码实例来加强对知识点的理解。

所谓,实践是检验真理的唯一标准,在编程领域也是同样遵守的,别看你对很多知识点的概念对答如流,但是给你段代码,你读不懂那也是不行的,此可谓“纸上谈兵”。

instanceof运算符

那么什么样的知识点讲解会更深入人心呢?我们来举个例子,谈谈你对instanceof操作符的理解。

这时你可能会说出一些专业的官方解释,instanceof运算符的用法是这样。

target instanceof constructor

你会解释说:构造函数 constructor() 的 prototype 属性是否出现在 target 对象的原型链中。

当我需要你用一句更接地气的话来解释时,该怎么说好呢?

我们一般可以这样说:判断一个实例target是否属于constructor对应的类型。这就好理解多了,其实就是判断实例和构造函数的关系。

常规操作

那我们来看下它的基本使用。

// 判断 foo 是否是 Foo 类的实例
function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo);  // true

你是不是在想这也太eazy了吧,这不是常规操作吗?

好好好,看官息怒,那我们来个难度大一点的,增加对继承的用法。

// 判断foo是否是Foo类的实例, 并且是否是其父类型的实例
function Poo(){}
function Foo(){}
Foo.prototype = new Poo(); // JavaScript 原型继承

var foo = new Foo();
console.log(foo instanceof Foo); // true
console.log(foo instanceof Poo); // true

这里我们增加了JavaScript原型继承,来增强对instanceof运算符的理解。

相信如果对instanceof有熟练掌握的同学,应该也难不倒他,foo可以很肯定的说是Foo类型的实例,而Foo又是继承于Poo,那么foo当然也可以说是Poo类型的实例了,因此会输出两个true。

王者操作

如果到这里,你以为instanceof运算符就只有这么点内容,那你就太小看它了。

我们再来点王者段位的考验。

console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true
console.log(Number instanceof Number); // false
console.log(String instanceof String); // false

console.log(Foo instanceof Function); // true
console.log(Foo instanceof Foo); // false

我相信大部分人看到上面的代码,都会一头雾水吧,心里还可能发出感叹:WTF?我零乱了。

看官别急,待我慢慢讲述。

按照官方的解释,instanceof运算符的计算过程会涉及到原型链的概念。关于原型链的具体讲解,可以看看我写的另一篇文章。

而在原型链的寻找过程中,就有两个很重要的属性prototype__pro__

prototype是挂在构造函数上的属性,而__pro__是挂在实例上的属性,实例的__pro__属性指向的就是构造函数的prototype属性。

function Foo() {}
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // true

有了这个知识点作为补充后,对于理解instanceof运算符就方便很多了。

它的实际计算过程可以用以下的代码来解释。

/**
* instanceof 运算符实现原理
* @param L 表示左表达式
* @param R 表示右表达式
* @returns {boolean}
*/
function instance_of(L, R) {
  var O = R.prototype; // 取 R 的显示原型
  L = L.__proto__; // 取 L 的隐式原型
  while (true) {
    if (L === null)
      return false;
    if (O === L) // 这里是重点:当 O 严格等于 L 时,返回“true”
      return true;
    L = L.__proto__; // 如果不相等则重新取 L 的隐式原型
  }
}

对于上面这段代码我们可以这么理解。

  • 获取右表达式R的prototype属性为O,左表达式的__proto__隐式原型为L。
  • 首先判断左表达式__proto__隐式原型L是否为空,如果为空,则直接返回“false”。实际上只有Object.prototype.__proto__属性为null,即到了原型链的最顶层。
  • 然后判断O与L是否严格相等,需要注意的是只有在严格相等的时候,才返回“true”。
  • 如果不相等,则递归L的__proto__属性,直到L为null或者O===L,得到最终结果。

当我们掌握到这里后,就可以小试牛刀看看一开始的那些问题的答案是怎么得到的了。

Object instanceof Object

我们基于上面instanceof运算符的原理,区分运算符左右两侧值,得到以下的计算过程。

// 将左、右侧值进行赋值
ObjectL = Object, ObjectR = Object;
// 根据原理获取对应值
L = ObjectL.__proto__ = Function.prototype;
R = ObjectR.prototype;
// 执行第一次判断
L != R;
// 继续寻找 L.__pro__
L = L.__proto__ = Function.prototype.__proto__ = Object.prototype;
// 执行第二次判断
L === R;

因此得到结果为true。

Function instanceof Function

同样我们可以得到以下的计算过程。

// 将左、右侧值进行赋值
FunctionL = Function, FunctionR = Function;
// 根据原理获取对应值
L = FunctionL.__proto__ = Function.prototype;
R = FunctionR.prototype = Function.prototype;
// 执行第一次判断成功,返回“true”
L === R;

因此得到的结果为true。

Foo instanceof Foo

针对Foo()构造函数,会存在 3 次链路寻找过程,详细过程如下。

// 将左、右侧值进行赋值
FooL = Foo, FooR = Foo;
// 根据原理获取对应值
L = FooL.__proto__ = Function.prototype;
R = FooR.prototype = Foo.prototype;
// 第一次判断失败,返回“false”
L !== R;
// 继续寻找 L.__proto__
L = L.__proto__ = Function.prototype.__proto__ = Object.prototype;
// 第二次判断失败,返回“false”
L !== R;
// 继续寻找 L.__proto__
L = L.__proto__ = Object.prototype.__proto__ = null;
// L 为 null, 返回“false”
L === null;

因此得到的结果为false。

相信通过这三个例子的讲解,大家已经能够完全掌握了instanceof运算符的用法了吧,如果以后再遇到与instanceof运算符有关的问题,大家都能够处理的游刃有余了。

怎么样能做的更好?

上面这种循序渐进式的讲解,再配合多个实例的练习,是不是可以很容易掌握到知识点的精髓呢?

而instanceof运算符只是偌大的JavaScript海洋中的冰山一角,试想一下如果有一本书能够将这些重点的知识汇集在一起,并通过丰富的实例来进行讲解,那对于JavaScript的学习该是多么幸福的一件事情呀。

由我个人编写,经由人民邮电出版社出版的书籍《JavaScript重难点实例解析》已经问世啦。

在这本书里,详细汇总了JavaScript中的各种重难点,并针对各个知识点进行丰富的代码实例验证,真正达到“授人以鱼不如授人以渔”的目的。

如果在阅读的过程中遇到什么问题,更是可以直接与我进行私信交流噢~