读《三元-JS灵魂之问》总结,给本身的一份原生JS补给(上)

前言

你盼世界,我盼望你无bug。Hello 你们好!我是霖呆呆!javascript

几个月前看过一遍三元大佬的《(建议收藏)原生JS灵魂之问, 请问你能接得住几个?》系列,当时是利用上下班公交的时间刷的。说下那时的感觉吧,有些知识点还真不知道,就感受好牛批,确实有一种被灵魂拷问的感受。最最可怕的是那时候尚未意识到本身的基础这么差,只是死记着一些零散的知识点,没几天可能就忘了,总想着何时好好理一下本身的知识体系却一直没有付出行动。html

在某一个点上,多是被某篇文章刺激的,忽然让个人心态发生了很大的改变。那种感受怎么形容呢...就像是我觉得本身都懂,可是我还不知道本身不懂,而后我还每天期盼着明天会更好。有点以为本身是井底之蛙吧。等我真正认清了本身以后才知道了沉淀这个词的重要性。当我带着'为何会这样?'、'还能够怎样?'、'若是这样会怎样?'的问题来回顾以前的一些知识,我发现本身要补充的真的还有不少...前端

这篇文章原本是本身近期再读《三元-JS灵魂之问》作的一些笔记,可是发现越记越多...也所以引出了我写的一系列文章,好比JS类型转换系列JS继承系列this等等。这里对三元提到的一些题作一些补充说明,使它们变得更适合初中级的小伙伴阅读吧,同时也是对本身这阶段学习的一个巩固,有写的不对的地方还请各位大佬指出。vue

写了一两年的掘金还没破Lv4,看来我要放大招了。在此立个flag,升Lv4后爆女装"呆妹",把"她"放到下篇文章安排一波。已经很卑微了...java

(秉承着对原做者神三元的感谢之情写的,还请三元的19177位粉丝不要误会呀,我本身也是他的一名小粉丝...)git

霖呆呆的知识体系

全部文章均被收入gitHub「niubility-coding-js」中。github

第一补: JS类型基础

1. '1'.toString()为何能够调用,1.toString()却不行?

咱们知道若是在代码中使用:面试

'1'.toString()
// 或者是
true.toString()
复制代码

都是能够正常调用的,这是由于toString它是Object.prototype上的方法,任何能访问到Object原型的元素均可以调用它。数组

而在此处,对于'1'.toString()至关因而作了一层转换,将其转为了一个"对象",这样就能够调用toString()方法了。浏览器

也就是这样:

var s = new Object('1');
s.toString();
s = null;
复制代码
  • 建立Object实例,将s变为了String{"1"}对象
  • 调用Object.prototype上的实例方法toString()
  • 用完以后当即销毁这个实例

这一部分三元分析的已经挺多了,我主要是想补充一下1.toString()为何就不行。

当咱们在代码中试图使用1.toString(),发现编辑器已经报错不容许咱们这样作了。

最开始会有这么奇怪的想法是由于咱们都忽视了一件事,那就是.它也是属于数字里的一部分啊 😂。

好比1.21.3。因此当你想要使用1.toString()的时候,JavaScript的解释器会把它做为数字的一部分,这样就至关于(1.)toString了,很显然这是一段错误的代码。

既然这样的话,若是我还给代码一个.是否是就能够了,因而我尝试了一下:

console.log(1.1.toString())
复制代码

发现它居然能正常打印出来:

"1.1"
复制代码

这也就再次证实了1.toString()会将.归给1所属,而不是归给toString()

固然若是你用的一个变量来承载这个数字的话也是能够的:

var num = 1;
console.log(num.toString()) // "1"

// 或者
console.log((1).toString()) // "1"
复制代码

2. 为何可使用new Number却不能使用new Symbol?

var num = new Number(1) // Number{1}
var str = new String('1') // String{'1'}
var bol = new Boolean(true) // Boolean{true}

var symbol = new Symbol(1) // TypeError
复制代码

向上面这种使用new Number、new String等建立的基本数据类型被称之为:围绕原始数据类型建立一个显式包装器对象

通俗点说就是:用new来建立基本类型的包装类。

而这种作法在ES6以后就不被支持了,从new Symbol(1)报错就能够看出来,如今使用的是不带new的方式:var symbol = Symbol(1),因此它做为构造函数来讲是不完整的。

可是由于历史遗留的缘由,new Number仍然能够这样用,不过并不推荐。

若是你真的想建立一个 Symbol 包装器对象 (Symbol wrapper object),你可使用 Object() 函数:

var sym = Symbol(1)
console.log(typeof sym) // "symbol"
var symObj = Object(sym)
console.log(typeof symObj) // "object"
复制代码

第二补:JS类型检测

1. instanceof可否判断基本数据类型?

什么意思呢 🤔️?

正常来讲,instanceof是用来判断某个对象的原型链上是否可以查找到某个构造函数的原型对象。

来看看通俗点的简介:

a instanceof B

实例对象a instanceof 构造函数B

检测a的原型链(__proto__)上是否有B.prototype,有则返回true,不然返回false

那么它能够用来判断基本数据类型吗?

也就是说我定义了一个var num = 1,我能够用instanceof来判断它是一个number类型的变量吗?

若是你试图这样写:

var num = 1;
console.log(num instanceof Number) // false
复制代码

发现结果是false

此时你能够用Symbol.hasInstance来实现一个自定义instanceof的行为。

首先想一想咱们是要实现一个什么功能?

a instanceof B
复制代码

左侧的a是一个变量,而右侧的B是一个构造函数,而class的本质也是一个构造函数。

因此在这个需求中,咱们能够定义一个叫作MyNumber的类,在其里面封装一层,暴露一个静态方法用来判断数据类型:

class MyNumber {
    static [Symbol.hasInstance](instance) {
        return typeof instance === 'number'
    }
}
var num = 1;
console.log(num instanceof MyNumber) // true
复制代码
  • 在类MyNumber中定义了一个名为Symbol.hasInstance的静态方法
  • 这个方法接收的是一个实例对象instance
  • 返回值为typeof判断是不是number类型

这里比较难理解的就是Symbol.hasInstance了,第一次接触它也不知道它是个啥 😂。找了一波MDN 上对它的介绍:

用于判断某对象是否为某构造器的实例。

而后试着写了几个案例,发现也不用把它想的那么复杂,你就简单理解,当咱们在使用instanceof的时候,可以自定义右侧构造函数(类)它的instanceof验证方式就能够了。

就像是上面👆那个案例同样,我在MyNumber重写了静态方法Symbol.hasInstance,让它的验证方式变成type instance === 'number'

那么为何是静态方法呢(也就是在方法前面加上static),经过阅读《🔥【何不三连】比继承家业还要简单的JS继承题-封装篇(牛刀小试)》咱们知道,静态方法是挂载在MyNumber这个类上的方法,所以咱们甚至能够把下面的内容换一种写法:

console.log(num instanceof MyNumber) // true
// 换成:
console.log(MyNumber[Symbol.hasInstance](num)) // true
复制代码

看到了吧,它其实就是一个方法名而已,而这个方法由于是静态的,在MyNumber上的,所以咱们能够用MyNumber[Symbol.hasInstance]这种方式调用。

想一想,若是没有static这个关键字呢?

没有static的话,定义在类里的方法就至关因而挂载到类的原型对象上,那么若是咱们想要使用它,一种就是直接用MyNumber.prototype调用,还有一种就是使用new MyNumber()生成一个实例来调用:

class MyNumber {
    [Symbol.hasInstance](instance) { // 没有 static
        return typeof instance === 'number'
    }
}
var num = 1

console.log(num instanceof new MyNumber()) // true
console.log(num instanceof MyNumber.prototype) // true

// 转化为:
console.log(MyNumber.prototype[Symbol.hasInstance](num)) // true
console.log(new MyNumber()[Symbol.hasInstance](num)) // true
复制代码

因此如今回过头来看看:

class MyNumber {
    static [Symbol.hasInstance](instance) {
        return typeof instance === 'number'
    }
}
var num = 1;
console.log(num instanceof MyNumber) // true
复制代码

是否是就好理解多了呢?

那么假如我如今想要你实现一个用instanceof判断是否是数组的类MyArray,该如何去写呢?

思考🤔...

唔...上答案:

class MyArray {  
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
console.log([] instanceof MyArray); // true
复制代码

2. instanceof的查找路线?

上面咱们说到了instanceof是用来判断某个对象的原型链上是否可以查找到某个构造函数的原型对象。

而且是会沿着原型链一层一层的向上查找,直到到达原型链的末位。

那这个过程具体是怎样的呢?让咱们来看一个例子🌰:

function Parent () {
  this.name = 'parent'
}
function Child () {
  this.sex = 'boy'
}
Child.prototype = new Parent()
var child1 = new Child()

console.log(child1 instanceof Child)
console.log(child1 instanceof Parent)
console.log(child1 instanceof Object)
复制代码

结果为:

true
true
true
复制代码

这里其实用到了原型链继承,Chind继承于Parent,并且三个构造函数的原型对象都存在于child1的原型链上。

也就是说,左边的child1它会向它的原型链中不停的查找,看有没有右边那个构造函数的原型对象。

例如child1 instanceof Child的查找顺序:

child1 -> child1.__proto__ -> Child.prototype
复制代码

child1 instanceof Parent的查找顺序:

child1 -> child1.__proto__ -> Child.prototype
-> Child.prototype.__proto__ -> Parent.prototype
复制代码

还不理解?

不要紧,我还有大招:

我在上面👆原型链继承的思惟导图上加了三个查找路线。

被⭕️标记的一、二、3分别表明的是Child、Parent、Object的原型对象。

好滴,一张图简洁明了。之后再碰到instanceof这种东西,按照我图上的查找路线来查找就能够了 😁 ~

3. isPropertypeOf()有什么做用?

既然说到了instanceof,那么就不得不提一下isPrototypeOf这个方法了。

它属于Object.prototype上的方法,这点你能够将Object.prototype打印在控制台中看看。

isPrototypeOf()的用法和instanceof相反。

它是用来判断指定对象object1是否存在于另外一个对象object2的原型链中,是则返回true,不然返回false

例如仍是上面👆这道题,咱们将要打印的内容改一下:

function Parent () {
  this.name = 'parent'
}
function Child () {
  this.sex = 'boy'
}
Child.prototype = new Parent()
var child1 = new Child()

console.log(Child.prototype.isPrototypeOf(child1))
console.log(Parent.prototype.isPrototypeOf(child1))
console.log(Object.prototype.isPrototypeOf(child1))
复制代码

这里输出的依然是三个true

true
true
true
复制代码

判断的方式只要把原型链继承instanceof查找思惟导图这张图反过来查找便可。

更多关于instanceOf的内容能够戳这里👇:

💦【何不三连】作完这48道题完全弄懂JS继承(1.7w字含辛整理-返璞归真)

4. Object.is()和===的区别?

  • 对于+0-0的判断不一样
  • 对于NaNNaN的判断不一样
console.log(+0 === -0) // true
console.log(Object.is(+0, -0)) // false
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true
复制代码

其实不须要特地的去记,你只须要理解,Object.is()===的基础上修复了这些特殊状况的失误。

也就是说:+0-0本就该不相等的,这点从1 / +0Infinity1 / -0-Infinity上能够看出。

(可是+00是没有区别的)

NaN表示的都是非数字,因此应该是相等的。

所以Object.is()的内部实际上是作了一些修复的处理:

function is (x, y) {
  if (x === y) {
    return x !== 0 || 1 / x === 1 / y;
  } else {
    return x !== x && y !== y;
  }
}
复制代码

这里能够怎样理解呢 🤔️?

  • x === y的时候,就是用来处理+0, -0, 0的特殊状况。

在这个判断中,首先会判断出x和y是否是都是+0, -0, 0中的其中一个(由于咱们知道不管是这三个中的哪个和0进行全等比较,结果都会是true,因此x !== 0就会是false),而根据||的短路原则,前面一项为false,那么最终的结果取决于后面一项。

因此x !== 0至关因而把x, y+0, -0, 0的状况推给了1 / x === 1 / y,用它的结果来决定最后的结果。

(在三元的原文中这段判断是这样写的return x !== 0 || y !== 0 || 1 /x === 1 / y,比我这里多了一个|| y !== 0。经评论区小伙伴matteokjh提示,以为能够把|| y !== 0的判断省略掉,另外MDN上对它的polyfill也是没有这一步的,我想了一下:由于能进入到x === y里,那么若是x !== 0 成立的话,那么y !== 0确定也成立了,貌似是能够省去。另外我把省去的代码执行了一下,和没有省去时的执行结果同样的,因此我感受可行。)

并且咱们又知道1 / +0Infinity1 / -0-Infinity,因此若是x, y+0或者-0的时候是不相等的,以此来实现Object.is(+0, -0)的结果为false

  • x !== y的时候,就是用来处理NaN, NaN的特殊状况。

由于咱们知道NaN !== NaN的,那么若是xy都不和它们本身相等的话,说明两个都是NaN了,而若是都是NaN的话,Object.is(NaN, NaN)的结果为true

第三补:toString()方法的妙用

1. toString()存在于哪里?

在此以前,我翻了不少关于toString()的资料,大多都是介绍了它的用法,可是它真正存在于哪里呢?

可能比较常见的一种说法是它存在于Object的原型对象中,也就是Object.prototype上,那么对于基本数据类型,Number、String、Boolean、 Symbol、BigInt呢?它们自身有这个方法吗?或者它们的原型对象上有吗?

本着一探到底的精神,我打印出了NumberNumber.prototype

console.log(Number)
console.log(Number.prototype)
复制代码

而后我发现了几件事:

  • Number只是一个构造函数,打印出来显示的会是源代码
  • Number.prototype上确实也有toString()
  • Number.prototype.__proto__也就是Object.prototype上也有toString()

而后我又试了一下String、Boolean、Symbol发现结果也和上面同样。

其实不难理解,看过《💦【何不三连】作完这48道题完全弄懂JS继承(1.7w字含辛整理-返璞归真)》的小伙伴都知道,全部对象的原型链到最后都会指向Object.prototype,算是都"继承"了Object的对象实例,所以都能使用toString()方法,可是对于不一样的内置对象为了能实现更适合自身的功能需求,都会重写该方法,因此你能够看到Number.prototype上也会有该方法。

因此咱们能够先得出第一个结论:

  • 除了null、undefined之外的其它数据类型(基本数据类型+引用数据类型),它们构造函数的原型对象上都有toString()方法
  • 基本数据类型构造函数原型对象上的toString()会覆盖Object原型对象上的toString()方法

(固然,等你看到后面你就会发现这种说法其实并不太准确,可是大多数时候咱们都只是关心谁能够用它,而不是它存在于哪里)

2. 谁能够调用toString()?

这个问题,其实在上面👆已经给出答案了,全部对象除了null、undefined之外的任何值均可以调用toString()方法,一般状况下它的返回结果和String同样。

其实这里,咱们最容易搞混的就是StringtoString

以前老是为了将某个类型转为字符串胡乱的用这两个属性。

  • String是一个相似于Function这样的对象,它既能够当成对象来用,用它上面的静态方法,也能够当成一个构造函数来用,建立一个String对象
  • toString它是除了null、undefined以外的数据类型都有的方法,一般状况下它的返回结果和String同样。

3. 如何用toString()判断某个数据的具体类型?

可能你们看的比较多的一种用法是这样的:

Object.prototype.toString.call({ name: 'obj' }) // '[object Object]'
复制代码

先来点硬知识,Object.prototype.toString这个方法会根据这个对象的[[class]]内部属性,返回由 "[object " 和 class 和 "]" 三个部分组成的字符串。

啥意思?[[class]]内部属性是个啥 🤔️?

这里你还真别想多,你就按字面意思来理解它就行了,想一想,class 英文单词的意思->

那好,我就认为它表明的是一类事物就好了。

就好比

  • 数组是一类,它的[[class]]Array
  • 字符串是一类,它的[[class]]String
  • arguments是一类,它的[[class]]Arguments

另外,关于[[class]]的种类是很是多的,你也不须要记住所有,只须要知道一些经常使用的,基本的,好理解的就能够了。

因此回到Object.prototype.toString.call()这种调用方式来,如今你能够理解它的做用了吧,它可以帮助咱们准确的判断某个数据类型,也就是辨别出是数组仍是数字仍是函数,仍是NaN。😊

另外鉴于它的返回结果是"[object Object]"这样的字符串,并且前面的"[object ]"这八个字符串都是固定的(包括"t"后面的空格),因此咱们是否是能够封装一个方法来只拿到"Object"这样的字符串呢?

很简单,上代码:

function getClass (obj) {
    let typeString = Object.prototype.toString.call(obj); // "[object Object]"
    return typeString.slice(8, -1);
}
复制代码

能够看到,我给这个函数命名为getClass,这也就呼应了它本来的做用,是为了拿到对象的[[class]]内部属性。

另外,在拿到了"[object Object]"字符串以后,是用了一个.slice(8, -1)的字符串截取功能,去除了前八个字符"[object ]"和最后一个"]"

如今让咱们来看看一些常见的数据类型吧:

function getClass(obj) {
    let typeString = Object.prototype.toString.call(obj); // "[object Array]"
    return typeString.slice(8, -1);
}
console.log(getClass(new Date)) // Date
console.log(getClass(new Map)) // Map
console.log(getClass(new Set)) // Set
console.log(getClass(new String)) // String
console.log(getClass(new Number)) // Number
console.log(getClass(true)) // Boolean
console.log(getClass(NaN)) // Number
console.log(getClass(null)) // Null
console.log(getClass(undefined)) // Undefined
console.log(getClass(Symbol(42))) // Symbol
console.log(getClass({})) // Object
console.log(getClass([])) // Array
console.log(getClass(function() {})) // Function
console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection

console.log(getClass(arguments)) // Arguments
复制代码

"霖呆呆,这么多,这是人干的事吗?"

"性平气和,记住一些经常使用的就好了..."

"啪!"

4. toString.call()与typeof的区别?

好滴👌,经过刚刚的学习,咱们了解到了,toString.call这种方式是为了获取某个变量更加具体的数据类型。

咦~说到数据类型,咱们原来不是有一个typeof吗?它和toString.call()又啥区别?

首先帮你们回顾一下typeof它的显示规则:

  • 对于原始类型来讲(也就是number、string这种),除了null均可以显示正确的类型
  • null由于历史版本的缘由被错误的判断为了"object"
  • 对于引用类型来讲(也就是object、array这种),除了函数都会显示为"object"
  • 函数会被显示为function

因此呀,typeof的缺点很明显啊,我如今有一个对象和一个数组,或者一个日期对象,我想要仔细的区分它,用typeof确定是不能实现的,由于它们获得的都是"object"

因此,采用咱们封装的getClass()显然是一个很好的选择。

5. 不一样类型的数据调用toString()会怎么样?

在不一样的数据类型调用toString()会有什么不一样呢?

这里我主要是分为两大块来讲:

  • 基本数据类型调用
  • 引用类型调用

5.1 基本数据类型调用toString

对于基本数据类型来调用它,超级简单的,你就想着就是把它的原始值换成了字符串而已:

console.log('1'.toString()) // '1'
console.log(1.1.toString()) // '1.1'
console.log(true.toString()) // 'true'
console.log(Symbol(1).toString()) // 'Symbol(1)'
console.log(10n.toString()) // '10'
复制代码

因此对于基本数据类型:

  • 原始数据类型调用时,把它的原始值换成了字符串

5.2 引用类型调用toString

比较难的部分是引用类型调用toString(),并且咱们知道引用类型根据[[class]]的不一样是分了不少类的,好比有ObjectArrayDate等等。

那么不一样类之间的toString()是否也不一样呢 🤔️?

没错,不一样版本的toString主要是分为:

  • 数组的toString方法是将每一项转换为字符串而后再用","链接
  • 普通的对象(好比{name: 'obj'}这种)转为字符串都会变为"[object Object]"
  • 函数(class)、正则会被转为源代码字符串
  • 日期会被转为本地时区的日期字符串
  • 原始值的包装对象调用toString会返回原始值的字符串
  • 拥有Symbol.toStringTag内置属性的对象在调用时会变为对应的标签"[object Map]"

(Symbol.toStringTag属性下一题会问到)

例如🌰:

console.log([].toString()) // ""
console.log([1, 2].toString()) // "1,2"

console.log({}.toString()) // "[object Object]"
console.log({name: 'obj'}.toString()) // "[object Object]"

console.log(class A {}.toString()) // "class A {}"
console.log(function () {}.toString()) // "function () {}"
console.log(/(\[|\])/g.toString()) // "/(\[|\])/g1"
console.log(new Date().toString()) // "Fri Mar 27 2020 12:33:16 GMT+0800 (中国标准时间)"

console.log(new Object(true).toString()) // "true"
console.log(new Object(1).toString()) // "1"
console.log(new Object(BigInt(10)).toString()) // "10"

console.log(new Map().toString()) // "[object Map]"
console.log(new Set().toString()) // "[object Set]"
复制代码

6. 了解Symbol.toStringTag吗?

《Symbol.toStringTag》上是这样描述它的:

Symbol.toStringTag公知的符号是在建立对象的默认字符串描述中使用的字符串值属性。它由该Object.prototype.toString()方法在内部访问。

看不懂不要紧,你这样理解就能够了,它其实就是决定了刚刚咱们提到全部数据类型中[[class]]这个内部属性是什么。

好比数字,咱们前面获得的[[class]]Number,那我就能够理解为数字这个类它的Symbol.toStringTag返回的就是Number

只不过在以前咱们用到的Number、String、Boolean中并无Symbol.toStringTag这个内置属性,它是在咱们使用toString.call()调用的时候才将其辨别返回。

而刚刚咱们刚刚在第五问看到的new Map(),让咱们把它打印出来看看。

console.log(new Map())
复制代码

能够看到Symbol.toStringTag它是确确实实存在于Map.prototype上的,也就是说它是Map、Set内置的一个属性,所以当咱们直接调用toString()的时候,就会返回"[object Map]"了。

额,咱们是否是就能够这样理解呢?

  • 没有Symbol.toStringTag内置属性的类型在调用toString()的时候至关因而String(obj)这样调用转换为相应的字符串
  • Symbol.toStringTag内置属性的类型在调用toString()的时候会返回相应的标签(也就是"[object Map]"这样的字符串)

咱们经常使用的带有Symbol.toStringTag内置属性的对象有:

console.log(new Map().toString()) // "[object Map]"
console.log(new Set().toString()) // "[object Set]"
console.log(Promise.resolve().toString()) // "[object Promise]"
复制代码

而它最主要的功能就是和Symbol.hasInstance同样,能够容许咱们自定义标签。

(Symbol.hasInsance的做用是自定义instanceof的返回值)

什么是自定义标签呢 🤔️?

也就是说,假如咱们如今建立了一个类,而且用toString.call()调用它的实例对象是会有以下结果:

class Super {}
console.log(Object.prototype.toString.call(new Super())) // "[object Object]"
复制代码

很好理解,由于产生的new Super()是一个对象嘛,因此打印出的会是"[object Object]"

可是如今有了Symbol.toStringTag以后,咱们能够改后面的"Object"

好比我重写一下:

class Super {
  get [Symbol.toStringTag] () {
    return 'Validator'
  }
}
console.log(Object.prototype.toString.call(new Super())) // "[object Validator]"
复制代码

这就是Symbol.toStringTag的厉害之处,它可以容许咱们自定义标签。

可是有一点要注意了,Symbol.toStringTag重写的是new Super()这个实例对象的标签,而不是重写Super这个类的标签,也就是说这里有区别的:

class Super {
  get [Symbol.toStringTag] () {
    return 'Validator'
  }
}
console.log(Object.prototype.toString.call(Super)) // "[object Function]"
console.log(Object.prototype.toString.call(new Super())) // "[object Validator]"
复制代码

由于Super它自己仍是一个函数,只有Super产生的实例对象才会用到咱们的自定义标签。

总结一下Symbol.toStringTag

  • 它是某些特定类型的内置属性,好比Map、Set、Promise
  • 主要做用是能够容许咱们自定义标签,修改Object.prototype.toString.call()的返回结果

更多关于toStringSymbol.toStringTag的内容能够戳这里👇:

【精】从206个console.log()彻底弄懂数据类型转换的前世此生(上)

第四补:JS类型转换

由于类型转换算是让人比较头疼的一部分,因此对于这一块我也专门写了系列文章,基本上覆盖了面试可能会问到的知识点,传送门:

【精】从206个console.log()彻底弄懂数据类型转换的前世此生(上)

【精】从206个console.log()彻底弄懂数据类型转换的前世此生(下)

(下篇写的挺好的没人看难受😣)

1. 说一下valueOf()的基本用法

  • 基本数据类型调用,返回调用者本来的值
  • 非日期对象的其它引用类型调用valueOf()默认是返回它自己
  • 而日期对象会返回一个1970 年 1 月 1 日以来的毫秒数(相似于1585370128307)。

例子🌰:

console.log('1'.valueOf()) // '1'
console.log(1.1.valueOf()) // 1.1

console.log([].valueOf()) // []
console.log({}.valueOf()) // {}
console.log(['1'].valueOf()) // ['1']
console.log(function () {}.valueOf()) // ƒ () {}
console.log(/(\[|\])/g.valueOf()) // /(\[|\])/g
console.log(new Date().valueOf()) // 1585370128307
复制代码

2. ToPrimitive的具体转换流程?

当咱们在将对象转换为原始类型或者进行==比较的时候,会调用内置的ToPrimitive函数。

好比:

console.log(String({})) // 对象转字符串,结果为 "[object Object]"
console.log(Number([1, 2])) // 对象转数字,结果为 NaN

console.log([] == ![]) // true
复制代码

以上结果的由来都通过了ToPrimitive函数。

先让咱们来看看它的函数语法:

ToPrimitive(input, PreferredType?)
复制代码

参数:

  • 参数一:input,表示要处理的输入值
  • 参数二:PerferredType,指望转换的类型,能够看到语法后面有个问号,表示是非必填的。它只有两个可选值,NumberString

而它对于传入参数的处理是比较复杂的,让咱们来看看流程图:

根据流程图,咱们得出了这么几个信息:

  1. 当不传入 PreferredType 时,若是 input 是日期类型,至关于传入 String,不然,都至关于传入 Number。
  2. 若是是 ToPrimitive(obj, Number),处理步骤以下:
  • 若是 obj 为 基本类型,直接返回
  • 不然,调用 valueOf 方法,若是返回一个原始值,则 JavaScript 将其返回。
  • 不然,调用 toString 方法,若是返回一个原始值,则 JavaScript 将其返回。
  • 不然,JavaScript 抛出一个类型错误异常。
  1. 若是是 ToPrimitive(obj, String),处理步骤以下:
  • 若是 obj为 基本类型,直接返回
  • 不然,调用 toString 方法,若是返回一个原始值,则 JavaScript 将其返回。
  • 不然,调用 valueOf 方法,若是返回一个原始值,则 JavaScript 将其返回。
  • 不然,JavaScript 抛出一个类型错误异常。

(总结来源《冴羽-JavaScript深刻之头疼的类型转换(上)》)

上面👆的图其实只是看着很复杂,细心的小伙伴可能会发现,在图里红框裱起来的地方,只有toString()valueOf()方法的执行顺序不一样而已。

若是 PreferredType 是 String 的话,就先执行 toString()方法

若是 PreferredType 是 Number 的话,就先执行 valueOf()方法

(霖呆呆建议你先本身在草稿纸上将这幅流程图画一遍)

2.1 对象转字符串

来点例子巩固一下:

console.log(String({})) // "[object Object]"
复制代码

对于这个简单的转换咱们能够把它换成toPrimitive的伪代码看看:

toPrimitive({}, 'string')
复制代码

OK👌,来回顾一下刚刚的转换规则:

  1. input{},是一个引用类型,PerferredTypestring
  2. 因此调用toString()方法,也就是{}.toString()
  3. {}.toString()的结果为"[object Object]",是一个字符串,为基本数据类型,而后返回,到此结束。

哇~

是否是一切都说得通了,好像不难吧 😁。

没错,当使用String()方法的时候,JS引擎内部的执行顺序确实是这样的,不过有一点和刚刚提到的步骤不同,那就是最后返回结果的时候,其实会将最后的基本数据类型再转换为字符串返回。

也就是说上面👆的第三步咱们得拆成两步来:

  1. {}.toString()的结果为"[object Object]",是一个字符串,为基本数据类型
  2. 将这个"[object Object]"字符串再作一次字符串的转换而后返回。(由于"[object Object]"已是字符串了,因此原样返回,这里看不出有什么区别)

将最后的结果再转换为字符串返回这一步,其实很好理解啊。你想一想,我调用String方法那就是为了获得一个字符串啊,你要是给我返回一个number、null啊什么的,那不是隔壁老王干的事嘛~

2.2 对象转数字

刚刚咱们说了对象转字符串也就是toPrimitive(object, 'string')的状况,

那么对象转数字就是toPrimitive(object, 'number')

区别就是转数字会先调用valueOf()后调用toString()

例子🌰:

console.log(Number({}))
console.log(Number([]))
console.log(Number([0]))
console.log(Number([1, 2]))

console.log(Number(new Date()))
复制代码

对于Number({})

  • 传入的是一个对象{},所以调用valueOf()方法,该方法在题7.1中已经提到过了,它除了日期对象的其它引用类型调用都是返回它自己,因此这里仍是返回了对象{}
  • valueOf()返回的值仍是对象,因此继续调用toString()方法,而{}调用toString()的结果为字符串"[object Object]",是一个基本数据类型
  • 获得基础数据类型了,该要返回了,不过在这以前还得将它在转换为数字才返回,那么"[object Object]"转为数字为NaN,因此结果为NaN

对于Number([])

  • 传入的是一个数组[],所以调用valueOf()方法,返回它自身[]
  • []继续调用toString()方法,而空数组转为字符串是为""
  • 最后再将空字符串""转为数字0返回

对于Number([0])

  • 由于[0]转为字符串是为"0",最后在转为数字0 返回

对于Number([1, 2])

  • 传入的是一个数组[1, 2],因此调用valueOf()方法返回的是数组自己[1,2]
  • 因此继续调用toString()方法,此时被转换为了"1,2"字符串
  • "1,2"字符串最后被转为数字为NaN,因此结果为NaN

对于Number(new Date())

  • 传入的是一个日期类型的对象new Date(),所以调用valueOf(),在题目7.2中已经说了,日期类型调用valueOf()是会返回一个毫秒数
  • 毫秒数为数字类型,也就是基本数据类型,那么直接返回(其实还有一步转为数字类型的过程),因此结果为1585413652137

结果:

console.log(Number({})) // NaN
console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2])) // NaN

console.log(Number(new Date())) // 1585413652137
复制代码

3. 数组转换为字符串为何会用","链接?

咱们都知道,当数组在进行转字符串的时候,会把里面的每一项都转为字符串而后再进行","拼接返回。

那么为何会有","拼接这一步呢?难道toString()在调用的时候还会调用join()方法吗?

为了验证个人想法💡,我作了一个实验,重写了一个数组的join()方法:

var arr = [1, 2]
arr['join'] = function () {
  let target = [...this]
  return target.join('~')
}
console.log(String(arr))
复制代码

重写的join函数中,this表示的就是调用的这个数组arr

而后将返回值改成"~"拼接,结果答案居然是:

"1~2"
复制代码

也就是说在String(arr)的过程当中,它确实是隐式调用了join方法。

可是当咱们重写了toString()以后,就不会管这个重写的join了:

var arr = [1, 2]
arr['toString'] = function () {
  let target = [...this]
  return target.join('*')
}
arr['join'] = function () {
  let target = [...this]
  return target.join('~')
}
console.log(String(arr)) // "1*2"
复制代码

能够看出toString()的优先级仍是比join()高的。

如今咱们又能够得出一个结论:

对象若是是数组的话,当咱们不重写其toString()方法,在转换为字符串类型的时候,默认实现就是将调用join()方法的返回值做为toString()的返回值。

4. 知道Symbol.toPrimitive吗?它有什么用?

当咱们在进行对象转原始值的时候,会隐式调用内部的ToPrimitive方法,按照前面说的那种方式进行转换。

Symbol.toPrimitive属性是能帮助咱们重写toPrimitive,以此来更改转换的结果。

让咱们来看看它的总结:

  • 若是重写了某个对象或者构造函数中的toString、valueOf、Symbol.toPrimitive方法,Symbol.toPrimitive的优先级是最高的
  • 如果Symbol.toPrimitive函数返回的值不是基础数据类型(也就是原始值),就会报错
  • Symbol.toPrimitive接收一个字符串参数hint,它表示要转换到的原始值的预期类型,一共有'number'、'string'、'default'三种选项
  • 使用String()调用时,hint'string';使用Number()时,hint'number'
  • hint参数的值从开始调用的时候就已经肯定了

来看个例子🌰:

var b = {
  toString () {
    console.log('toString')
    return '1'
  },
  valueOf () {
    console.log('valueOf')
    return [1, 2]
  },
  [Symbol.toPrimitive] (hint) {
    console.log('symbol')
    if (hint === 'string') {
      console.log('string')
      return '1'
    }
    if (hint === 'number') {
      console.log('number')
      return 1
    }
    if (hint === 'default') {
      console.log('default')
      return 'default'
    }
  }
}
console.log(String(b))
console.log(Number(b))
复制代码

这道题重写了toString、valueOf、Symbol.toPrimitive三个属性,经过上面👆的题目咱们已经知道了只要有Symbol.toPrimitive在,前面两个属性就被忽略了,因此咱们不用管它们。

而对于Symbol.toPrimitive,我将三种hint的状况都写上了,若是按照个人设想的话,在调用String(b)的时候应该是要打印出string的,调用Number(b)打印出number,结果也正如我所预想的同样:

'string'
'1'
'number'
1
复制代码

5. ==在进行比较时的类型转换?

其实在实际中咱们被考的比较多的可能就是用==来比较判断两个不一样类型的变量是否相等。

而全等===的状况比较简单,通常不太会考,由于全等的条件就是:若是类型相等值也相等才认为是全等,并不会涉及到类型转换。

可是==的状况就相对复杂了,先给你们看几个比较眼熟的题哈:

console.log([] == ![]) // true
console.log({} == true) // false
console.log({} == "[object Object]") // true
复制代码

怎样?这几题是否是常常看到呀 😁,下面就让咱们一个一个来看。

首先,咱们仍是得清楚几个概念,这个是硬性规定的,不看的话咱无法继续下去啊。

当使用==进行比较的时候,会有如下转换规则(判断规则):

  1. 两边类型若是相同,值相等则相等,如 2 == 3确定是为false的了
  2. 比较的双方都为基本数据类型:
  • 如果一方为null、undefined,则另外一方必须为null或者undefined才为true,也就是null == undefinedtrue或者null == nulltrue,由于undefined派生于null
  • 其中一方为String,是的话则把String转为Number再来比较
  • 其中一方为Boolean,是的话则将Boolean转为Number再来比较
  1. 比较的一方有引用类型:
  • 将引用类型遵循ToNumber的转换形式来进行比较(实际上它的hintdefault,也就是toPrimitive(obj, 'default'),可是default的转换规则和number很像)
  • 两方都为引用类型,则判断它们是否是指向同一个对象

在一些文章中,会说道:

若是其中一方为Object,且另外一方为String、Number或者Symbol,会将Object转换成字符串,再进行比较

(摘自《神三元-(建议收藏)原生JS灵魂之问, 请问你能接得住几个?(上)》中的3. == 和 ===有什么区别?)

这样认为其实也能够,由于想一想toPrimitive(obj, 'number')的过程:

  • 如果输入值为引用数据类型,则先调用valueOf()方法
  • 如果valueOf()方法的返回值是基本数据类型则直接返回,若不是则继续调用toString()
  • 如果调用toString()的返回值是基本数据类型则返回,不然报错。

能够看到,首先是会执行valueOf()的,可是引用类型执行valueOf()方法,除了日期类型,其它状况都是返回它自己,也就是说执行完valueOf()以后,仍是一个引用类型而且是它自己。那么咱们是否是就能够将valueOf()这一步给省略掉,认为它是直接执行toString()的,这样作起题来也快了不少。

6. 几种一元运算符的类型转换?

对于几种经常使用运算符的类型转换:

  1. -、*、/、%这四种都会把符号两边转成数字来进行运算
  2. +因为不只是数字运算符,仍是字符串的链接符,因此分为两种状况:
  • 两端都是数字则进行数字计算(一元正号+b这种状况至关于转换为数字)
  • 有一端是字符串,就会把另外一端也转换为字符串进行链接

对象的+号类型转换:

  • 对象在进行+号字符串链接的时候,toPrimitive的参数hintdefault,可是default的执行顺序和number同样都是先判断有没有valueOf,有的话执行valueOf,而后判断valueof后的返回值,如果是引用类型则继续执行toString。(相似题4.54.6)
  • 日期在进行+号字符串链接的时候,优先调用toString()方法。(相似题4.7)
  • 一元正号是转换其余对象到数值的最快方法,也是最推荐的作法,由于 它不会对数值执行任何多余操做

7. 你会几种让if(a == 1 && a == 2 && a == 3)条件成立的办法?

这道题相信你们看的不会少,除了重写valueOf()你还会哪些解法呢?

解法一:重写valueOf()

这个解法是利用了:当对象在进行==比较的时候实际是会先执行valueOf(),如果valueOf()的返回值是基本数据类型就返回,不然仍是引用类型的话就会继续调用toString()返回,而后判断toString()的返回值,如果返回值为基本数据类型就返回,不然就报错。

如今valueOf()每次返回的是一个数字类型,因此会直接返回。

// 1
var a = {
  value: 0,
  valueOf () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

解法二:重写valueOf()toString()

var a = {
  value: 0,
  valueOf () {
    return {}
  },
  toString () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

原理就是解法一的原理,只不过用到了当valueOf()的返回值是引用类型的时候会继续调用toString()

这里你甚至均可以不用重写valueOf(),由于除了日期对象其它对象在调用valueOf()的时候都是返回它自己。

也就是说你也能够这样作:

var a = {
  value: 0,
  toString () {
    return ++this.value
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

解法三:重写Symbol.toPrimitive

想一想是否是还能够用Symbol.toPrimitive来解呢?

结合题3.10咱们知道,当对象在进行==比较的时候,Symbol.toPrimitive接收到的参数hint"defalut",那么咱们只须要这样重写:

var a = {
  value: 0,
  [Symbol.toPrimitive] (hint) {
    if (hint === 'default') {
      return ++this.value
    }
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

这样结果也是能够的。

解法四:定义class并重写valueOf()

固然你还能够用class来写:

class A {
  constructor () {
    this.value = 0
  }
  valueOf () {
    return ++this.value
  }
}
var a = new A()
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

解法五:利用数组转为字符串会隐式调用join()

什么 ? 还有别的解法吗?并且我看解法五的题目有点没看懂啊。

让咱们回过头去看看题4.3,那里提到了当数组在进行转字符串的时候,调用toString()的结果其实就是调用join的结果。

那和这道题有什么关系?来看看答案:

let a = [1, 2, 3]
a['join'] = function () {
  return this.shift()
}
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

由于咱们知道,对象若是是数组的话,当咱们不重写其toString()方法,在转换为字符串类型的时候,默认实现就是将调用join()方法的返回值做为toString()的返回值。

因此这里咱们重写了ajoin方法,而此次重写作了两件事情:

  1. 将数组a执行a.shift()方法,咱们知道这会影响原数组a的,将第一项去除
  2. 将刚刚去除的第一项返回回去

因此当咱们在执行a == 1这一步的时候,因为隐式调用了a['join']方法,因此会执行上面👆说的那两件事情,后面的a == 2a == 3同理。

解法六:定义class继承Array并重写join()

对于解法五咱们一样能够用class来实现

class A extends Array {
  join = this.shift
}
var a = new A(1, 2, 3)
if (a == 1 && a == 2 && a == 3) {
  console.log('成立')
}
复制代码

这种写法比较酷🆒,可是第一次看可能不太能懂。

  • 首先A这个类经过extends继承于Array,这样经过new A建立的就是一个数组
  • 而后A重写了join方法,join = this.shift就至关因而join = function () { return this.shift() }
  • 这样当每次调用a == xxx的时候,都会隐式调用咱们自定义的join方法,执行和解法五同样的操做。

8. 让if (a === 1 && a === 2 && a === 3)条件成立?

这道题看着和上面那道有点像,不过这里判断的条件是全等的。

咱们知道全等的条件:

  1. 左右两边的类型要相等,若是类型不相等则直接返回false,这点和==不一样,==会发生隐式类型转换
  2. 再判断值相不相等

而对于上面👆一题的解法咱们都是利用了==会发生隐式类型转换这一点,显然若是再用它来解决这道题是不能实现的。

想一想当咱们在进行a === xxx判断的时候,实际上就是调用了a这个数据而已,也就是说咱们要在调用这个数据以前,作一些事情,来达到咱们的目的。

不知道这样说有没有让你想到些什么 🤔️?或许你和呆呆同样会想到Vue大名鼎鼎的数据劫持 😁。

想一想在Vue2.x中不就是利用了Object.defineProperty()方法从新定义data中的全部属性,那么在这里咱们一样也能够利用它来劫持a,修改a变量的get属性。

var value = 1;
Object.defineProperty(window, "a", {
  get () {
    return this.value++;
  }
})
if (a === 1 && a === 2 && a === 3) {
  console.log('成立')
}
复制代码

这里实际就作了这么几件事情:

  • 使用Object.defineProperty()方法劫持全局变量window上的属性a
  • 当每次调用a的时候将value自增,并返回自增后的值

(其实我还想着用Proxy来进行数据劫持,代理一下window,将它用new Proxy()处理一下,可是对于window对象好像没有效果...)

解法二

怎么办 😂,一碰到这种题我又想到了数组...

var arr = [1, 2, 3];
Object.defineProperty(window, "a", {
  get () {
    return this.arr.shift()
  }
})
if (a === 1 && a === 2 && a === 3) {
  console.log('成立')
}
复制代码

中了shift()的毒...固然,这样也是能够实现的。

解法三

还有就是EnoYao大佬那里看来的骚操做:

原文连接:juejin.im/post/5e66dc…

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if (aᅠ == 1 && a == 2 && ᅠa == 3) {
  console.log("成立");
}
复制代码

说来惭愧...a的先后隐藏的字符我打不来 😂...

9. 控制台输入{}+[]会怎样?

这道有趣的题是从LINGLONG的一篇《【js小知识】[]+ {} =?/{} +[] =?(关于加号的隐式类型转换)》那里看来的。

(PS: pick一波玲珑,这位小姐姐的文章写的都挺好的,不过热度都不高,你们能够支持一下呀 😁)

OK👌,来看看题目是这样的:

在控制台(好比浏览器的控制台)输入:

{}+[]
复制代码

的结果会是什么 🤔️?

咦~这道题应该很简单吧,根据前面的类型转换原则,+两边都转换为字符串,{}转为"[object Object]"[]转为"",拼接的结果就是:

console.log({}+[]) // "[object Object]"
复制代码

可是注意这里的题目,是要在控制台输出哦。

此时我把这段代码在控制台输出结果发现答案居然和预期的不同:

{}+[]
0
复制代码

也就是说{}被忽略了,直接执行了+[],结果为0

知道缘由的我眼泪掉了下来,原来它和以前提到的1.toString()有点像,也是由于JS对于代码解析的缘由,在控制台或者终端中,JS会认为大括号{}开头的是一个空的代码块,这样看着里面没有内容就会忽略它了。

因此只执行了+[],将其转换为0

若是咱们换个顺序的话就不会有这种问题:

(图片来源:juejin.im/post/5e6055…)

为了证明这一点,咱们能够把{}当成空对象来调用一些对象的方法,看会有什么效果:

(控制台或者终端)

{}.toString()
复制代码

如今的{}依旧被认为是代码块而不是一个对象,因此会报错:

Uncaught SyntaxError: Unexpected token '.'
复制代码

解决办法能够用一个()将它扩起来:

({}).toString
复制代码

不过这东西在实际中用的很少,我能想到的一个就是在项目中(好比我用的vue),而后定义props的时候,若是其中一个属性的默认值你是想要定义为一个空对象的话,就会用到:

props: {
    target: {
        type: Object,
        default: () => ({})
    }
}
复制代码

第五补:八种JS继承

对于JS继承我也写了一个系列「封装|继承|多态」,这里是传送门:

具体的例子还有题目在文章中都已经说的很清楚了,这里我就只列举一下各个继承的优缺点以及伪代码。

1. 原型链继承

伪代码:

Child.prototype = new Parent()
复制代码

思惟导图:

优势:

  • 继承了父类的模板,又继承了父类的原型对象

缺点:

  • 若是要给子类的原型上新增属性和方法,就必须放在Child.prototype = new Parent()这样的语句后面
  • 没法实现多继承(由于已经指定了原型对象了)
  • 来自原型对象的全部属性都被共享了,这样若是不当心修改了原型对象中的引用类型属性,那么全部子类建立的实例对象都会受到影响
  • 建立子类时,没法向父类构造函数传参数

2. 构造继承

伪代码:

function Child () {
    Parent.call(this, ...arguments)
}
复制代码

思惟导图:

优势:

  • 解决了原型链继承中子类实例共享父类引用对象的问题,实现多继承,建立子类实例时,能够向父类传递参数

缺点:

  • 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
  • 实例并非父类的实例,只是子类的实例
  • 没法实现函数复用,每一个子类都有父类实例函数的副本,影响性能

3. 组合继承

伪代码:

// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
// 原型链继承
Child.prototype = new Parent()
// 修正constructor
Child.prototype.constructor = Child
复制代码

思惟导图:

实现方式:

  • 使用原型链继承来保证子类能继承到父类原型中的属性和方法
  • 使用构造继承来保证子类能继承到父类的实例属性和方法

优势:

  • 能够继承父类实例属性和方法,也可以继承父类原型属性和方法
  • 弥补了原型链继承中引用属性共享的问题
  • 可传参,可复用

缺点:

  • 使用组合继承时,父类构造函数会被调用两次
  • 而且生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,因此增长了没必要要的内存。

4. 寄生组合继承

伪代码:

// 构造继承
function Child () {
  Parent.call(this, ...arguments)
}
// 原型式继承
Child.prototype = Object.create(Parent.prototype)
// 修正constructor
Child.prototype.constructor = Child
复制代码

思惟导图:

寄生组合继承算是ES6以前一种比较完美的继承方式吧。

它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。

因此它拥有了上述全部继承方式的优势:

  • 只调用了一次父类构造函数,只建立了一份父类属性
  • 子类能够用到父类原型链上的属性和方法
  • 可以正常的使用instanceOfisPrototypeOf方法

5. 原型式继承

伪代码:

var child = Object.create(parent)
复制代码

实现方式:

该方法的原理是建立一个构造函数,构造函数的原型指向对象,而后调用 new 操做符建立实例,并返回这个实例,本质是一个浅拷贝。

ES5以后能够直接使用Object.create()方法来实现,而在这以前就只能手动实现一个了(如题目6.2)。

优势:

  • 再不用建立构造函数的状况下,实现了原型链继承,代码量减小一部分。

缺点:

  • 一些引用数据操做的时候会出问题,两个实例会公用继承实例的引用数据类
  • 谨慎定义方法,以避免定义方法也继承对象原型的方法重名
  • 没法直接给父级构造函数使用参数

6. 寄生式继承

伪代码:

function createAnother (original) {
    var clone = Object.create(original);; // 经过调用 Object.create() 函数建立一个新对象
    clone.fn = function () {}; // 以某种方式来加强对象
    return clone; // 返回这个对象
}
复制代码

实现方式:

  • 原型式继承的基础上再封装一层,来加强对象,以后将这个对象返回。

优势:

  • 再不用建立构造函数的状况下,实现了原型链继承,代码量减小一部分。

缺点:

  • 一些引用数据操做的时候会出问题,两个实例会公用继承实例的引用数据类
  • 谨慎定义方法,以避免定义方法也继承对象原型的方法重名
  • 没法直接给父级构造函数使用参数

7. 混入方式继承

伪代码:

function Child () {
    Parent.call(this)
    OtherParent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Object.assign(Child.prototype, OtherParent.prototype)
Child.prototype.constructor = Child
复制代码

思惟导图:

8. class中的继承

伪代码:

class Child extends Parent {
    constructor (...args) {
        super(...args)
    }
}
复制代码

ES6中的继承:

  • 主要是依赖extends关键字来实现继承,且继承的效果相似于寄生组合继承
  • 使用了extends实现继承不必定要constructorsuper,由于没有的话会默认产生并调用它们
  • extends后面接着的目标不必定是class,只要是个有prototype属性的函数就能够了

ES5继承和ES6继承的区别:

  • ES5中的继承(例如构造继承、寄生组合继承) ,实质上是先创造子类的实例对象this,而后再将父类的属性和方法添加到this上(使用的是Parent.call(this))。
  • 而在ES6中却不是这样的,它实质是先创造父类的实例对象this(也就是使用super()),而后再用子类的构造函数去修改this

参考文章

知识无价,支持原创。

参考文章:

后语

你盼世界,我盼望你无bug。这篇文章就介绍到这里。

因为开篇已经说了太多话了这里就不说了🙊。

喜欢霖呆呆的小伙还但愿能够关注霖呆呆的公众号 LinDaiDai 或者扫一扫下面的二维码👇👇👇.

我会不定时的更新一些前端方面的知识内容以及本身的原创文章🎉

你的鼓励就是我持续创做的主要动力 😊.

相关推荐:

《全网最详bpmn.js教材》

《【建议改为】读完这篇你还不懂Babel我给你寄口罩》

《【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)》

《【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)》

《【何不三连】比继承家业还要简单的JS继承题-封装篇(牛刀小试)》

《【何不三连】作完这48道题完全弄懂JS继承(1.7w字含辛整理-返璞归真)》

《【精】从206个console.log()彻底弄懂数据类型转换的前世此生(上)》