JS 对于每位前端开发都是必备技能,在小册中我们也会有多个章节去讲述这部分的知识。首先我们先来熟悉下 JS 的一些常考和容易混乱的基础知识点。
[h1]原始(Primitive)类型[/h1]涉及面试题:原始类型有哪几种?null 是对象嘛? 在 JS 中,存在着 6 种原始值,分别是:
首先原始类型存储的都是值,是没有函数可以调用的,比如
此时你肯定会有疑问,这不对呀,明明是可以使用的。其实在这种情况下,已经不是原始类型了,而是被强制转换成了类型也就是对象类型,所以可以调用函数。
除了会在必要的情况下强转类型以外,原始类型还有一些坑。
其中 JS 的类型是浮点类型的,在使用中会遇到某些 Bug,比如,但是这一块的内容会在进阶部分讲到。类型是不可变的,无论你在类型上调用何种方法,都不会对值有改变。
另外对于来说,很多人会认为他是个对象类型,其实这是错误的。虽然会输出,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,开头代表是对象,然而表示为全零,所以将它错误的判断为。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
[h1]对象(Object)类型[/h1]涉及面试题:对象类型和原始类型的不同之处?函数参数是对象会发生什么问题? 在 JS 中,除了原始类型那么其他的都是对象类型了。对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。对于常量来说,假设内存地址(指针)为,那么在地址的位置存放了值,常量存放了地址(指针),再看以下代码- const a = []const b = ab.push(1)
复制代码 当我们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针),也就是说当前变量存放的地址(指针)也是,当我们进行数据修改的时候,就会修改存放在地址(指针)上的值,也就导致了两个变量的值都发生了改变。
接下来我们来看函数参数是对象的情况- function test(person) { person.age = 26 person = { name: 'yyy', age: 30 } return person}const p1 = { name: 'yck', age: 25}const p2 = test(p1)console.log(p1) // -> ?console.log(p2) // -> ?
复制代码 对于以上代码,你是否能正确的写出结果呢?接下来让我为你解析一番:
- 首先,函数传参是传递对象指针的副本
- 到函数内部修改参数的属性这步,我相信大家都知道,当前的值也被修改了
- 但是当我们重新为分配了一个对象时就出现了分歧,请看下图
所以最后拥有了一个新的地址(指针),也就和没有任何关系了,导致了最终两个变量的值是不相同的。
[h1]typeof vs instanceof[/h1]涉及面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么? 对于原始类型来说,除了都可以显示正确的类型- typeof 1 // 'number'typeof '1' // 'string'typeof undefined // 'undefined'typeof true // 'boolean'typeof Symbol() // 'symbol'
复制代码 对于对象来说,除了函数都会显示,所以说并不能准确判断变量到底是什么类型- typeof [] // 'object'typeof {} // 'object'typeof console.log // 'function'
复制代码 如果我们想判断一个对象的正确类型,这时候可以考虑使用,因为内部机制是通过原型链来判断的,在后面的章节中我们也会自己去实现一个。- const Person = function() {}const p1 = new Person()p1 instanceof Person // truevar str = 'hello world'str instanceof String // falsevar str1 = new String('hello world')str1 instanceof String // true
复制代码 对于原始类型来说,你想直接通过来判断类型是不行的,当然我们还是有办法让判断原始类型的- class PrimitiveString { static [Symbol.hasInstance](x) { return typeof x === 'string' }}console.log('hello world' instanceof PrimitiveString) // true
复制代码 你可能不知道是什么东西,其实就是一个能让我们自定义行为的东西,以上代码等同于- typeof 'hello world' === 'string'
复制代码 ,所以结果自然是了。这其实也侧面反映了一个问题,也不是百分之百可信的。
[h1]类型转换[/h1]涉及面试题:该知识点常在笔试题中见到,熟悉了转换规则就不惧怕此类题目了。 首先我们要知道,在 JS 中类型转换只有三种情况,分别是:
我们先来看一个类型转换表格,然后再进入正题
[h2]转Boolean[/h2]在条件判断时,除了,,,,,,,其他所有值都转为,包括所有对象。
[h2]对象转原始类型[/h2]对象在转换类型的时候,会调用内置的函数,对于该函数来说,算法逻辑一般来说如下:
- 如果已经是原始类型了,那就不需要转换了
- 调用,如果转换为基础类型,就返回转换的值
- 调用,如果转换为基础类型,就返回转换的值
- 如果都没有返回原始类型,就会报错
当然你也可以重写,该方法在转原始类型时调用优先级最高。- let a = { valueOf() { return 0 }, toString() { return '1' }, [Symbol.toPrimitive]() { return 2 }}1 + a // => 3
复制代码 [h2]四则运算符[/h2]加法运算符不同于其他几个运算符,它有以下几个特点:
- 运算中其中一方为字符串,那么就会把另一方也转换为字符串
- 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
- 1 + '1' // '11'true + true // 24 + [1,2,3] // "41,2,3"
复制代码 如果你对于答案有疑问的话,请看解析:
- 对于第一行代码来说,触发特点一,所以将数字转换为字符串,得到结果
- 对于第二行代码来说,触发特点二,所以将转为数字
- 对于第三行代码来说,触发特点二,所以将数组通过转为字符串,得到结果
另外对于加法还需要注意这个表达式因为等于,所以结果为,你可能也会在一些代码中看到过的形式来快速获取类型。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字- 4 * '3' // 124 * [] // 04 * [1, 2] // NaN
复制代码 [h2]比较运算符[/h2]- 如果是对象,就通过转换对象
- 如果是字符串,就通过字符索引来比较
- let a = { valueOf() { return 0 }, toString() { return '1' }}a > -1 // true
复制代码 在以上代码中,因为是对象,所以会通过转换为原始类型再比较值。
[h1]this[/h1]涉及面试题:如何正确判断 this?箭头函数的 this 是什么? 是很多人会混淆的概念,但是其实它一点都不难,只是网上很多文章把简单的东西说复杂了。在这一小节中,你一定会彻底明白这个概念的。
我们先来看几个函数调用的场景- function foo() { console.log(this.a)}var a = 1foo()const obj = { a: 2, foo: foo}obj.foo()const c = new foo()
复制代码 接下来我们一个个分析上面几个场景
- 对于直接调用来说,不管函数被放在了什么地方,一定是
- 对于来说,我们只需要记住,谁调用了函数,谁就是,所以在这个场景下函数中的就是对象
- 对于的方式来说,被永远绑定在了上面,不会被任何方式改变
说完了以上几种情况,其实很多代码中的应该就没什么问题了,下面让我们看看箭头函数中的- function a() { return () => { return () => { console.log(this) } }}console.log(a()()())
复制代码 首先箭头函数其实是没有的,箭头函数中的只取决包裹箭头函数的第一个普通函数的。在这个例子中,因为包裹箭头函数的第一个普通函数是,所以此时的是。另外对箭头函数使用这类函数是无效的。
最后种情况也就是这些改变上下文的 API 了,对于这些函数来说,取决于第一个参数,如果第一个参数为空,那么就是。
那么说到,不知道大家是否考虑过,如果对一个函数进行多次,那么上下文会是什么呢?- let a = {}let fn = function () { console.log(this) }fn.bind().bind(a)() // => ?
复制代码 如果你认为输出结果是,那么你就错了,其实我们可以把上述代码转换成另一种形式- // fn.bind().bind(a) 等于let fn2 = function fn1() { return function() { return fn.apply() }.apply(a)}fn2()
复制代码 可以从上述代码中发现,不管我们给函数几次,中的永远由第一次决定,所以结果永远是。- let a = { name: 'yck' }function foo() { console.log(this.name)}foo.bind(a)() // => 'yck'
复制代码 以上就是的规则了,但是可能会发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定最终指向哪里。
首先,的方式优先级最高,接下来是这些函数,然后是这种调用方式,最后是这种调用方式,同时,箭头函数的一旦被绑定,就不会再被任何方式所改变。
如果你还是觉得有点绕,那么就看以下的这张流程图吧,图中的流程只针对于单个规则。
[h1]小结[/h1]以上就是我们 JS 基础知识点的第一部分内容了。这一小节中涉及到的知识点在我们日常的开发中经常可以看到,并且很多容易出现的坑 也出自于这些知识点,相信认真读完的你一定会在日后的开发中少踩很多坑。如果大家对于这个章节的内容存在疑问,欢迎在评论区与我互动。
在任何时代,
教育说起来都是一件高大上的事,
但却没有什么真正有价值的东西是教得会的,
没有任何一种文化模因
可以说清楚一个个体的全部问题。
在任何时代,
想要抓住人性的弱点来赚钱都非常容易,
没有一点高级。
相反,想要建设一种文化,
耐心地拆除信息壁垒,
并且能够坚持下来,
那真不是一般的不易。
在任何时代,
在一秒钟内看到本质的人,
和花半辈子看不清的人,
自然是不一样的命运。
现在扫描下方二位码
添加我的微信
每一天,你将受到才哥的理论,结合历史、政治、文化、艺术、商业故事,令人防不胜防的高纯度无死角知识轰炸。以及,不间断的私藏书籍、电影推荐。
有时候,某件事虽记不清楚,但总感觉这样的事在很久很久以前发生过...
有时候,某个人虽从未见过,但总感觉面前的人在另一个时空里曾遇见...
那时候相忘于江湖的事,或许穿越了时光,有了新的世界
那时候不远万里追寻的梦,也许穿行过人海,也有了新的意义
而对于我们来说,那个惦念的江湖,那个执着的梦,其核心都是在偌大的世界里,寻找到更真实更好的自己!
知道你会来
所以我一直在这里等
岁月不饶人,我亦未曾绕过岁月
最后,大家有什么想要吐槽的,就在留言区说出你的想法。
|
|