内置类型
JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object)。
基本类型有六种:
null
undefined
number
string
boolean
symbol(ES6)
其中 JS 的数字类型是浮点类型的,没有整型。并且浮点类型基于 IEEE 754
标准实现,在使用中会遇到某些 Bug。NaN
也属于 number
类型,并且 NaN
不等于自身。
0.1 + 0.2 // 0.30000000000000004
对于基本类型来说,如果使用字面量的方式,那么这个变量只是个字面量,只有在必要的时候才会转换为对应的类型
let a = 111 // 这只是字面量,不是 number 类型
a.toString() // 使用时候才会转换为对象类型
对象(Object)是引用类型,在使用过程中会遇到浅拷贝和深拷贝的问题。
let a = { name: 'FE' }
let b = a
b.name = 'EF'
console.log(a.name) // EF
判断类型
使用 typeof
// 1.使用typeof判断数据类型
// 基本类型
// 除了null都可以被检测出来
typeof 147 // number
typeof 'Rock' // string
typeof true // boolean
typeof undefined // undefined
typeof Symbol(777) // symbol
// 判断null会返回object
typeof null // object
// 但是要注意一点
null instanceof Object // false
// 引用类型
typeof {} // object
// 其他类型
typeof new Function() // function
typeof new Array() // object
typeof new Date() // object
使用 Object.prototype.toString.call()
// 2.使用toString()进行类型检测
const printTypeByToString = val => {
let regex = /\[object\s(\w+)\]/
let res = Object.prototype.toString.call(val)
let newRes = res.replace(regex, (match, p1) => p1.toLocaleLowerCase())
return newRes
}
// 基本类型
printTypeByToString(147) // number
printTypeByToString('Rock') // string
printTypeByToString(true) // boolean
printTypeByToString(Symbol(777)) // symbol
printTypeByToString(null) // null
printTypeByToString(undefined) // undefined
// 引用类型
printTypeByToString({}) // object
// 其他类型
printTypeByToString([]) // array
printTypeByToString(new Date()) // date
printTypeByToString(new Function()) // function
参考:
类型转换
Number,String,Boolean 转各种总结
1 | 0 | '' | 'abc' | null | undefined | true | false | NaN | |
---|---|---|---|---|---|---|---|---|---|
Number | 1 | 0 | 0 | NaN | 0 | NaN | 1 | 0 | NaN |
String | '1' | '0' | '' | 'abc' | 'null' | 'undefined' | 'true' | 'false' | 'NaN' |
Boolean | true | false | false | true | false | false | true | false | false |
转 Boolean
// 转换结果为false的
Boolean(null)
Boolean(undefined)
Boolean(false)
Boolean(NaN)
Boolean('')
Boolean(0)
Boolean(-0)
对象转基本类型
// TODO 有待商榷
// 对象会先执行 toPrimitive操作 即valueOf和toString()
// 返回 '[object Object] '
Number({}) // NaN
String({}) // "[object Object]"
Boolean({}) // true
数组转换基本类型
// 对象会先执行 toPrimitive操作 即valueOf和toString()
// 返回一个字符串
// 然后在进行Number
Number([]) // 0
Number([1]) // 0
Number([1, 2]) // NaN
String([]) // ''
String([1]) // '1'
String([1, 2]) // '1,2'
// 除了null,undefined,false,'',NaN,false其他通过Boolean进行转子那个都会返回true
// 所以Boolean一个数组返回true
Boolean([]) // true
Boolean([1]) // true
Boolean([1, 2]) // true
四则运算
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
1 + '1' // '11'
2 * '2'[(1, 2)] + // 4
[1, 2] +
[2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'
对于加号需要注意这个表达式 'a' + + 'b'
'a' + +'b' // -> "aNaN"
// 一元操作符
// 因为 + 'b' -> NaN
// 你也许在一些代码中看到过 + '1' -> 1
有可能出现!,要注意!的优先级高于四则运算
1 + !1 // 1
1 + !0 // 2
==操作符
三句口诀
- 字符串类型和数字类型相等比较,字符串强转数字,再比较。
- 其他类型和布尔类型相等比较,布尔类型强转数字,再比较。
- 对象和非对象相等比较,执行
ToPrimitive
操作,再比较。
// 为什么 [] == ![] 是true
// 1. 由于!的优先级高先执行右侧即 ![] => !Boolean([]) => !true => false
// 2. [] == false 当一个对象和其他类型比较时先将对象通过 ToPrimitive 操作转换成基本类型在比较
// 什么是ToPrimitive操作呢,其实ToPrimitive操作就是先执行valueOf()方法,如果结果为原始值,则返回此结果;否则,执行toString()方法,如果结果是原始值,则返回此结果;否则,抛出TypeError错误。 所以[].toString => ""
// 3. "" == false 当一个布尔值与其他类型比较先将布尔值转换成数字类型 false => 0
// 4. "" == 0 当一个数字与字符串比较时先将字符串转换成数字类型 Number("") => 0
// 5. 0 == 0 所以[] == ![]
// 为什么 true == "true" 为false
// 1. 存在布尔值时现将布尔值转成数字 Boolean(true) => 1
// 2. 存在数字和字符串的时候将字符串转换成 数字 Number("true") => NaN
// 3. 1 != NaN 所以true == "true" 为false
// 为什么 "[object Object]" == {} 为 true
// 因为{}转换成基本类型会通过toString 返回"[object Object]"
参考:
比较运算符
- 如果是对象,就通过
toPrimitive
转换对象 - 如果是字符串,就通过
unicode
字符索引来比较
arguments
arguments
对象是所有(非箭头)函数中都可用的局部变量。你可以使用 arguments
对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引 0 处。
// 获取长度
function getLength() {
console.log(this.length)
}
function testArg(first, second) {
// 获取实参的个数
getLength.call(arguments) // 3
// 获取形参的个数
getLength.call(arguments.callee) // 2
}
testArg(1, 2, 3)
// 获取形参的个数
getLength.call(testArg) // 2
arguments
是一个类数组对象。具有数组的特性比如拥有 length 属性、可以通过索引获取对应的项。但是并不能使用数组的方法比如 pop()
、shift()
、slice()
等等,但是可以通过 [].pop.call(arguments)
进行调用
function argUseArrFun() {
// ES6转化数组
// 1.Array.from
let args = Array.from(arguments)
args.forEach(item => console.log(item))
// 2.解构赋值
// let args = [...arguments]
// args.forEach(item => console.log(item))
// 对类数组对象使用数组方法
let firstArg = [].shift.call(arguments) // 'Student'
let isNameExist = [].includes.call(arguments, 'Jack') // true
}
argUseArrFun('Jack', 15, 'Boy', 'Student')
原型
参考:
new
实现 new 的过程
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 返回新对象
// 构造函数
function People(name, age) {
this.name = name
this.age = age
}
// 模拟new操作
function newOwn(consFoo, args) {
let obj = {}
obj.__proto__ == consFoo.prototype
// 以上两步等价于 let obj = Object.create(consFoo.prototype)
let res = consFoo.apply(obj, args)
return typeof res === 'object' ? res : obj
}
// 实例化
let people = newOwn(People, ['Jack', 18])
参考:
https://blog.fundebug.com/2017/06/02/javascript-new-for-beginner/
instanceof
instanceof
可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
。
function myInstanceof(leftVaule, rightVaule) {
// 取右表达式的 prototype 值
let rightProto = rightVaule.prototype
// 取左表达式的__proto__值
leftVaule = leftVaule.__proto__
while (true) {
if (leftVaule === null) {
return false
}
if (leftVaule === rightProto) {
return true
}
leftVaule = leftVaule.__proto__
}
}
this
- 函数直接加圆括号调用,this 是 window 对象
- 对象打点调用函数,函数的上下文是这个对象
- 从数组中中枚举出函数,上下文是数组
- 定时器或者延时器调用函数,上下文是 window 对象
- 事件处理函数的 this 是触发事件的这个元素
- IIFE 中的上下文无条件是 window
var name = 'window name'
function getName() {
console.log(this.name)
}
var obj = {
name: 'object name',
getName: function () {
console.log(this.name)
}
}
getName() // window name
obj.getName() // object name
var fun = obj.getName
fun() // window name
fun.call(obj) // object name
深浅拷贝
自己实现
// 类型判断
const getValType = val => {
let str = Object.prototype.toString.call(val)
let reg = /\[object\s(\w+)\]/
let res = str.replace(reg, (match, p1) => p1.toLocaleLowerCase())
return res
}
// 浅拷贝
const shallowCopy = obj => {
let type = getValType(obj)
if (type !== 'object' && type !== 'array') return
let newObj = type === 'object' ? {} : []
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
// 深拷贝
const deepCopy = obj => {
let type = getValType(obj)
if (type !== 'object' && type !== 'array') return
let newObj = type === 'object' ? {} : []
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = getValType(obj[key]) === 'object' ? deepCopy(obj[key]) : obj[key]
}
}
return newObj
}
一些骚招
-
浅拷贝
- 数组
- concat()
- slice()
- ...展开符
- 对象
- Object.assign()
- ...展开符
- 数组
-
深拷贝
-
通用 JSON.parse(JSON.stringify( ))
但是该方法也是有局限性的:
- 会忽略
undefined
- 不能序列化函数
- 不能解决循环引用的对象
- 会忽略
-