JS 基础知识点及常考面试题

论坛 期权论坛 期权     
李才哥   2019-7-8 00:35   1993   0



JS 对于每位前端开发都是必备技能,在小册中我们也会有多个章节去讲述这部分的知识。首先我们先来熟悉下 JS 的一些常考和容易混乱的基础知识点。
[h1]原始(Primitive)类型[/h1]
涉及面试题:原始类型有哪几种?null 是对象嘛?
在 JS 中,存在着 6 种原始值,分别是:
    1. boolean
    复制代码
    1. null
    复制代码
    1. undefined
    复制代码
    1. number
    复制代码
    1. string
    复制代码
    1. symbol
    复制代码
首先原始类型存储的都是值,是没有函数可以调用的,比如
  1. undefined.toString()
复制代码


此时你肯定会有疑问,这不对呀,明明
  1. '1'.toString()
复制代码
是可以使用的。其实在这种情况下,
  1. '1'
复制代码
已经不是原始类型了,而是被强制转换成了
  1. String
复制代码
类型也就是对象类型,所以可以调用
  1. toString
复制代码
函数。
除了会在必要的情况下强转类型以外,原始类型还有一些坑。
其中 JS 的
  1. number
复制代码
类型是浮点类型的,在使用中会遇到某些 Bug,比如
  1. 0.1 + 0.2 !== 0.3
复制代码
,但是这一块的内容会在进阶部分讲到。
  1. string
复制代码
类型是不可变的,无论你在
  1. string
复制代码
类型上调用何种方法,都不会对值有改变。
另外对于
  1. null
复制代码
来说,很多人会认为他是个对象类型,其实这是错误的。虽然
  1. typeof null
复制代码
会输出
  1. object
复制代码
,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,
  1. 000
复制代码
开头代表是对象,然而
  1. null
复制代码
表示为全零,所以将它错误的判断为
  1. object
复制代码
。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
[h1]对象(Object)类型[/h1]
涉及面试题:对象类型和原始类型的不同之处?函数参数是对象会发生什么问题?
在 JS 中,除了原始类型那么其他的都是对象类型了。对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。
  1. const a = []
复制代码
对于常量
  1. a
复制代码
来说,假设内存地址(指针)为
  1. #001
复制代码
,那么在地址
  1. #001
复制代码
的位置存放了值
  1. []
复制代码
,常量
  1. a
复制代码
存放了地址(指针)
  1. #001
复制代码
,再看以下代码
  1. const a = []const b = ab.push(1)
复制代码
当我们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针),也就是说当前变量
  1. b
复制代码
存放的地址(指针)也是
  1. #001
复制代码
,当我们进行数据修改的时候,就会修改存放在地址(指针)
  1. #001
复制代码
上的值,也就导致了两个变量的值都发生了改变。
接下来我们来看函数参数是对象的情况
  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) // -> ?
复制代码
对于以上代码,你是否能正确的写出结果呢?接下来让我为你解析一番:
  • 首先,函数传参是传递对象指针的副本
  • 到函数内部修改参数的属性这步,我相信大家都知道,当前
    1. p1
    复制代码
    的值也被修改了
  • 但是当我们重新为
    1. person
    复制代码
    分配了一个对象时就出现了分歧,请看下图


所以最后
  1. person
复制代码
拥有了一个新的地址(指针),也就和
  1. p1
复制代码
没有任何关系了,导致了最终两个变量的值是不相同的。
[h1]typeof vs instanceof[/h1]
涉及面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?
  1. typeof
复制代码
对于原始类型来说,除了
  1. null
复制代码
都可以显示正确的类型
  1. typeof 1 // 'number'typeof '1' // 'string'typeof undefined // 'undefined'typeof true // 'boolean'typeof Symbol() // 'symbol'
复制代码
  1. typeof
复制代码
对于对象来说,除了函数都会显示
  1. object
复制代码
,所以说
  1. typeof
复制代码
并不能准确判断变量到底是什么类型
  1. typeof [] // 'object'typeof {} // 'object'typeof console.log // 'function'
复制代码
如果我们想判断一个对象的正确类型,这时候可以考虑使用
  1. instanceof
复制代码
,因为内部机制是通过原型链来判断的,在后面的章节中我们也会自己去实现一个
  1. instanceof
复制代码
  1. 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
复制代码
对于原始类型来说,你想直接通过
  1. instanceof
复制代码
来判断类型是不行的,当然我们还是有办法让
  1. instanceof
复制代码
判断原始类型的
  1. class PrimitiveString {  static [Symbol.hasInstance](x) {    return typeof x === 'string'  }}console.log('hello world' instanceof PrimitiveString) // true
复制代码
你可能不知道
  1. Symbol.hasInstance
复制代码
是什么东西,其实就是一个能让我们自定义
  1. instanceof
复制代码
行为的东西,以上代码等同于
  1. typeof 'hello world' === 'string'
复制代码
,所以结果自然是
  1. true
复制代码
了。这其实也侧面反映了一个问题,
  1. instanceof
复制代码
也不是百分之百可信的。
[h1]类型转换[/h1]
涉及面试题:该知识点常在笔试题中见到,熟悉了转换规则就不惧怕此类题目了。
首先我们要知道,在 JS 中类型转换只有三种情况,分别是:
  • 转换为布尔值
  • 转换为数字
  • 转换为字符串
我们先来看一个类型转换表格,然后再进入正题


[h2]转Boolean[/h2]在条件判断时,除了
  1. undefined
复制代码
  1. null
复制代码
  1. false
复制代码
  1. NaN
复制代码
  1. ''
复制代码
  1. 0
复制代码
  1. -0
复制代码
,其他所有值都转为
  1. true
复制代码
,包括所有对象。
[h2]对象转原始类型[/h2]对象在转换类型的时候,会调用内置的
  1. [[ToPrimitive]]
复制代码
函数,对于该函数来说,算法逻辑一般来说如下:
  • 如果已经是原始类型了,那就不需要转换了
  • 调用
    1. x.valueOf()
    复制代码
    ,如果转换为基础类型,就返回转换的值
  • 调用
    1. x.toString()
    复制代码
    ,如果转换为基础类型,就返回转换的值
  • 如果都没有返回原始类型,就会报错
当然你也可以重写
  1. Symbol.toPrimitive
复制代码
,该方法在转原始类型时调用优先级最高。
  1. let a = {  valueOf() {    return 0  },  toString() {    return '1'  },  [Symbol.toPrimitive]() {    return 2  }}1 + a // => 3
复制代码
[h2]四则运算符[/h2]加法运算符不同于其他几个运算符,它有以下几个特点:
  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串
  1. 1 + '1' // '11'true + true // 24 + [1,2,3] // "41,2,3"
复制代码
如果你对于答案有疑问的话,请看解析:
  • 对于第一行代码来说,触发特点一,所以将数字
    1. 1
    复制代码
    转换为字符串,得到结果
    1. '11'
    复制代码
  • 对于第二行代码来说,触发特点二,所以将
    1. true
    复制代码
    转为数字
    1. 1
    复制代码
  • 对于第三行代码来说,触发特点二,所以将数组通过
    1. toString
    复制代码
    转为字符串
    1. 1,2,3
    复制代码
    ,得到结果
    1. 41,2,3
    复制代码
另外对于加法还需要注意这个表达式
  1. 'a' + + 'b'
复制代码
  1. 'a' + + 'b' // -> "aNaN"
复制代码
因为
  1. + 'b'
复制代码
等于
  1. NaN
复制代码
,所以结果为
  1. "aNaN"
复制代码
,你可能也会在一些代码中看到过
  1. + '1'
复制代码
的形式来快速获取
  1. number
复制代码
类型。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字
  1. 4 * '3' // 124 * [] // 04 * [1, 2] // NaN
复制代码
[h2]比较运算符[/h2]
  • 如果是对象,就通过
    1. toPrimitive
    复制代码
    转换对象
  • 如果是字符串,就通过
    1. unicode
    复制代码
    字符索引来比较
  1. let a = {  valueOf() {    return 0  },  toString() {    return '1'  }}a > -1 // true
复制代码
在以上代码中,因为
  1. a
复制代码
是对象,所以会通过
  1. valueOf
复制代码
转换为原始类型再比较值。
[h1]this[/h1]
涉及面试题:如何正确判断 this?箭头函数的 this 是什么?
  1. this
复制代码
是很多人会混淆的概念,但是其实它一点都不难,只是网上很多文章把简单的东西说复杂了。在这一小节中,你一定会彻底明白
  1. this
复制代码
这个概念的。
我们先来看几个函数调用的场景
  1. function foo() {  console.log(this.a)}var a = 1foo()const obj = {  a: 2,  foo: foo}obj.foo()const c = new foo()
复制代码
接下来我们一个个分析上面几个场景
  • 对于直接调用
    1. foo
    复制代码
    来说,不管
    1. foo
    复制代码
    函数被放在了什么地方,
    1. this
    复制代码
    一定是
    1. window
    复制代码
  • 对于
    1. obj.foo()
    复制代码
    来说,我们只需要记住,谁调用了函数,谁就是
    1. this
    复制代码
    ,所以在这个场景下
    1. foo
    复制代码
    函数中的
    1. this
    复制代码
    就是
    1. obj
    复制代码
    对象
  • 对于
    1. new
    复制代码
    的方式来说,
    1. this
    复制代码
    被永远绑定在了
    1. c
    复制代码
    上面,不会被任何方式改变
    1. this
    复制代码
说完了以上几种情况,其实很多代码中的
  1. this
复制代码
应该就没什么问题了,下面让我们看看箭头函数中的
  1. this
复制代码
  1. function a() {  return () => {    return () => {      console.log(this)    }  }}console.log(a()()())
复制代码
首先箭头函数其实是没有
  1. this
复制代码
的,箭头函数中的
  1. this
复制代码
只取决包裹箭头函数的第一个普通函数的
  1. this
复制代码
。在这个例子中,因为包裹箭头函数的第一个普通函数是
  1. a
复制代码
,所以此时的
  1. this
复制代码
  1. window
复制代码
。另外对箭头函数使用
  1. bind
复制代码
这类函数是无效的。
最后种情况也就是
  1. bind
复制代码
这些改变上下文的 API 了,对于这些函数来说,
  1. this
复制代码
取决于第一个参数,如果第一个参数为空,那么就是
  1. window
复制代码

那么说到
  1. bind
复制代码
,不知道大家是否考虑过,如果对一个函数进行多次
  1. bind
复制代码
,那么上下文会是什么呢?
  1. let a = {}let fn = function () { console.log(this) }fn.bind().bind(a)() // => ?
复制代码
如果你认为输出结果是
  1. a
复制代码
,那么你就错了,其实我们可以把上述代码转换成另一种形式
  1. // fn.bind().bind(a) 等于let fn2 = function fn1() {  return function() {    return fn.apply()  }.apply(a)}fn2()
复制代码
可以从上述代码中发现,不管我们给函数
  1. bind
复制代码
几次,
  1. fn
复制代码
中的
  1. this
复制代码
永远由第一次
  1. bind
复制代码
决定,所以结果永远是
  1. window
复制代码
  1. let a = { name: 'yck' }function foo() {  console.log(this.name)}foo.bind(a)() // => 'yck'
复制代码
以上就是
  1. this
复制代码
的规则了,但是可能会发生多个规则同时出现的情况,这时候不同的规则之间会根据优先级最高的来决定
  1. this
复制代码
最终指向哪里。
首先,
  1. new
复制代码
的方式优先级最高,接下来是
  1. bind
复制代码
这些函数,然后是
  1. obj.foo()
复制代码
这种调用方式,最后是
  1. foo
复制代码
这种调用方式,同时,箭头函数的
  1. this
复制代码
一旦被绑定,就不会再被任何方式所改变。
如果你还是觉得有点绕,那么就看以下的这张流程图吧,图中的流程只针对于单个规则。
[h1]小结[/h1]以上就是我们 JS 基础知识点的第一部分内容了。这一小节中涉及到的知识点在我们日常的开发中经常可以看到,并且很多容易出现的坑 也出自于这些知识点,相信认真读完的你一定会在日后的开发中少踩很多坑。如果大家对于这个章节的内容存在疑问,欢迎在评论区与我互动。
在任何时代,
教育说起来都是一件高大上的事,
但却没有什么真正有价值的东西是教得会的,
没有任何一种文化模因
可以说清楚一个个体的全部问题。
在任何时代,
想要抓住人性的弱点来赚钱都非常容易,
没有一点高级。
相反,想要建设一种文化,
耐心地拆除信息壁垒,
并且能够坚持下来,
那真不是一般的不易。
在任何时代,
在一秒钟内看到本质的人,
和花半辈子看不清的人,
自然是不一样的命运。
现在扫描下方二位码
添加我的微信


     每一天,你将受到才哥的理论,结合历史、政治、文化、艺术、商业故事,令人防不胜防的高纯度无死角知识轰炸。以及,不间断的私藏书籍、电影推荐。


      有时候,某件事虽记不清楚,但总感觉这样的事在很久很久以前发生过...

     有时候,某个人虽从未见过,但总感觉面前的人在另一个时空里曾遇见...
    那时候相忘于江湖的事,或许穿越了时光,有了新的世界
     那时候不远万里追寻的梦,也许穿行过人海,也有了新的意义
     而对于我们来说,那个惦念的江湖,那个执着的梦,其核心都是在偌大的世界里,寻找到更真实更好的自己!
知道你会来
所以我一直在这里等



岁月不饶人,我亦未曾绕过岁月

最后,大家有什么想要吐槽的,就在留言区说出你的想法。





分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:30
帖子:6
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP