JS基础知识点

原始(Primitive)类型和对象(Object)类型
  • 6种原始数据类型:boolean、null、undefined、number、string、symbol
  • null是原始数据类型,但typeof null 会输出 object,这是由于在JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
  • number是浮点类型,所以在使用中会有类似 0.1 + 0.2 !== 0.3 这种bug。
  • string是固定不变的,无论调用何种方法都不会对值有改变。
  • symbol是es6新增的,不是用来存储数据的,而是作为独一无二的一个key,放置数据的,防止数据因为重复而无法存进数据对象中。
  • 对象数据类型:和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。
  • 们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针),所以两个变量的值会一起改变。
typeof
  • 原始数据类型
  • 除了null,都会显示正确的类型,null显示的是 object
  • 对象数据类型
  • 除了函数都会显示object,对应函数显示的是function
instanceof
  • 使用instanceof判断对象类型的内部机制是通过原型链
  • 不能直接通过instanceof判断原始类型
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimitiveString) // true
  • Symbol.hasInstance是一个能让我们自定义 instanceof 行为的东西,,以上代码等同于 typeof ‘hello world’ === ‘string’ ,所以结果自然是 true 了。
类型转换

JS基础知识点_数据类型

  • 转换为布尔值
  • 在条件判断时,除了 undefined , null , false , NaN , ‘’ , 0 , -0 ,其他所有值都转为 true ,包括所有对象。
  • 转换为数字
  • 转换为字符串
  • 对象转原始类型时:
  • 调用 x.valueOf() ,转换为数字(原始类型)
  • 调用 x.toString() ,转换为字符串(原始类型)
  • 重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高
四则运算符
  • 加法运算符不同于其他几个运算符,它有以下几个特点:
  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串。
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串。
  • 对于加法还需要注意这个表达式 ‘a’ + + ‘b’,最后的结果是’aNaN’,因为+ ‘b’ 的值是NaN,按照字符串拼接就是 ‘aNaN’。
  • 对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
比较运算符
  • 如果是对象,就通过 toPrimitive 转换对象
  • 如果是字符串,就通过 unicode 字符索引来比较
let a = {
valueOf() {
return 0
},
toString() {
return '1'
}
}
a > -1 // true

上述代码中,a是一个对象,所以通过 valueOf 转换为原始类型再比较值。

this

JS基础知识点_字符串_02

== 和 === 的区别
  • '=='会进行强制类型转换,具体如下图:
  • '==='其实就是直接判断两者类型和值是否相同
闭包
  • 闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数A 中的变量,那么函数 B 就是闭包。
  • 闭包存在的意义就是让我们可以间接访问函数内部的变量
  • 经典面试题目中,循环中使用闭包解决var定义函数的问题:

    因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。
  • 解决方法有三个:使用闭包;使用setTimeout的第三个变量;将var改成let
  • 使用闭包:
for(var i=1;i<=5;i++){

;(function(j){

setTimeout(function timer() {

console.log(j)

},j*1000)

})(i)

}
  • 直接将最后一个参数(第三个)改写成i即可
深浅拷贝
  • 浅拷贝:只会拷贝所有的属性值到新的对象中,并不是共用同一个地址,但对于属性值也是对象的情况,就会拷贝地址。
  • 通过Object.assign实现:
let a = {
age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
  • 通过展开运算符 … 来实现:
let a = {
age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1
  • 注意:浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到最开始的话题了,两者享有相同的地址。
  • 深拷贝
  • 通过 JSON.parse(JSON.stringify(object)) 解决:
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

局限性:会忽略 undefined;会忽略 symbol;不能序列化函数;不能解决循环引用的对象。如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel。

原型
  • 每个 JS 对象都有 proto 属性,这个属性指向了原型。这个属性在现在来说已经不推荐直接去使用它了,这只是浏览器在早期为了让我们访问到内部属性 [[prototype]] 来实现的一个东西。
  • 对于obj 来说,可以通过 proto 找到一个原型对象,在该对象中定义了很多函数让我们来使用。
  • 原型的 constructor 属性指向构造函数,构造函数又通过 prototype 属性指回原型,但是并不是所有函数都具有这个属性, Function.prototype.bind() 就没有这个属性。
  • Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它。
  • Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它
  • 函数的 prototype 是一个对象。
  • 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链。原型链就是多个对象通过 proto 的方式连接了起来。