skip to content

JavaScript
JavaScript 基础随想

内置类型

JS 中分为七种内置类型,七种内置类型又分为两大类型:基本类型和对象(Object)。

基本类型有六种:

  • null
  • undefined
  • number
  • string
  • boolean
  • symbol(ES6)

其中 JS 的数字类型是浮点类型的,没有整型。并且浮点类型基于 IEEE 754 标准实现,在使用中会遇到某些 BugNaN 也属于 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 转各种总结

10'''abc'nullundefinedtruefalseNaN
Number100NaN0NaN10NaN
String'1''0''''abc''null''undefined''true''false''NaN'
Booleantruefalsefalsetruefalsefalsetruefalsefalse

转 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

==操作符

三句口诀

  1. 字符串类型和数字类型相等比较,字符串强转数字,再比较。
  2. 其他类型和布尔类型相等比较,布尔类型强转数字,再比较。
  3. 对象和非对象相等比较,执行 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]"

参考:

http://www.cnblogs.com/WhiteBlade/p/6754772.html

https://www.cnblogs.com/imwtr/p/4392041.html

比较运算符

  1. 如果是对象,就通过 toPrimitive 转换对象
  2. 如果是字符串,就通过 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')

原型

参考:

https://www.jianshu.com/p/aa2f885ba871

https://github.com/KieSun/Blog/issues/2

new

实现 new 的过程

  1. 新生成了一个对象
  2. 链接到原型
  3. 绑定 this
  4. 返回新对象
// 构造函数
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

  1. 函数直接加圆括号调用,this 是 window 对象
  2. 对象打点调用函数,函数的上下文是这个对象
  3. 从数组中中枚举出函数,上下文是数组
  4. 定时器或者延时器调用函数,上下文是 window 对象
  5. 事件处理函数的 this 是触发事件的这个元素
  6. 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
}

一些骚招

  • 浅拷贝

    • 数组
      1. concat()
      2. slice()
      3. ...展开符
    • 对象
      1. Object.assign()
      2. ...展开符
  • 深拷贝

    • 通用 JSON.parse(JSON.stringify( ))

      但是该方法也是有局限性的:

      • 会忽略 undefined
      • 不能序列化函数
      • 不能解决循环引用的对象