JavaScript进阶知识点

本文内容主要是面试复习准备、查漏补缺、深入某知识点的引子、了解相关面试题及底下涉及到的知识点,都是一些面试题底下的常用知识点,而不是甩一大堆面试题给各位,结果成了 换个题形就不会的那种

自定义事件

自定义事件可以传参的和不可以传参的定义方式不一样,看代码吧

// 注册事件  不可以添加参数
let eve1 = new Event("myClick")
// 可以添加参数 
let eve2 = new CustomEvent('myClick',params)

// 监听事件
dom.addEventListener("myClick",function () {
    console.log("myClick")
})

// 触发事件
dom.dispatchEvent(eve1)

 var、let、const

区别 var let const
是否有块级作用域 × ✔️ ✔️
是否有变量声明提升 ✔️ ✔️ ✔️
是否可以重复声明 ✔️ × ×
是否可以重新赋值 ✔️ ✔️ ×
是否必须设置初始值 × × ✔️
是否添加全局属性 ✔️ × ×
是否存在暂时性死区 × ✔️ ✔️

暂时性死区:创建了变量(有变量提升),但是没有初始化,没法使用变量,直接使用就会进入暂时性死区

Set、Map

Set 和 Map 都是强引用(下面有说明),都可以遍历,比如 for of / forEach

Set

允许存储任何类型的唯一值,只有键值(key)没有键名,常用方法 addsizehasdelete等等,看下用法

const set1 = new Set()
set1.add(1)
const set2 = new Set([1,2,3])
set2.add('沐华')
console.log(set1) // { 1 }

console.log(set2) // { 1, 2, 3, '沐华' }
console.log(set2.size) // 4
console.log(set2.has('沐华')) // true
set2.delete('沐华')
console.log(set2) // { 1, 2, 3 }

// 用 Set 去重

const set3 = new Set([1, 2, 1, 1, 3, 2])
const arr = [...set3]
console.log(set3) // { 1, 2, 3 }
console.log(arr) // [1, 2, 3]

// 引用类型指针不一样,无法去重
const set4 = new Set([1, { name: '沐华' }, 1, 2, { name: '沐华' }])
console.log(set4) // { 1, { name: '沐华' }, 2, { name: '沐华' } }

// 引用类型指针一样,就可以去重
const obj = { name: '沐华' }
const set5 = new Set([1, obj, 1, 2, obj])
console.log(set5) // { 1, { name: '沐华' }, 2 }

 Map

是键值对的集合;常用方法 setgetsizehasdelete等等,看下用法

const map1 = new Map()
map1.set(0, 1)
map1.set(true, 2)
map1.set(function(){}, 3)
const map2 = new Map([ [0, 1], [true, 2], [{ name: '沐华' }, 3] ])
console.log(map1) // {0 => 1, true => 2, function(){} => 3}
console.log(map2) // {0 => 1, true => 2, {…} => 3}

console.log(map1.size) // 3
console.log(map1.get(true)) // 2
console.log(map1.has(true)) // true
map1.delete(true)
console.log(map1) // {0 => 1, function(){} => 3}

 WeakSet、WeakMap

WeakSetWeakMap 都是弱引用,对 GC 更加友好,都不能遍历

比如: let obj = {}

就默认创建了一个强引用的对象,只有手动将 obj = null,在没有引用的情况下它才会被垃圾回收机制进行回收,如果是弱引用对象,垃圾回收机制会自动帮我们回收,某些情况下性能更有优势,比如用来保存 DOM 节点,不容易造成内存泄漏

WeakSet

成员只能是对象或数组,方法只有 addhasdelete,看下用法

const ws1 = new WeakSet()
let obj = { name: '沐华' }
ws1.add(obj)
ws1.add(function(){})
console.log(ws1) // { function(){}, { name: '沐华' } }
console.log(ws1.has(obj)) // true

ws1.delete(obj)
console.log(ws1.has(obj)) // false

 WeakMap

键值对集合,只能用对象作为 key(null 除外),value 可以是任意的。方法只有 getsethasdelete,看下用法

const wm1 = new WeakMap()

const o1 = { name: '沐华' },
      o2 = function(){},
      o3 = window

wm1.set(o1, 1) // { { name: '沐华' } : 1 } 这样的键值对
wm1.set(o2, undefined)
wm1.set(o1, o3); // value可以是任意值,包括一个对象或一个函数
wm1.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象

wm1.get(o1); // 1 获取键值

wm1.has(o1); // true  有这个键名
wm1.has(o2); // true 即使值是undefined

wm1.delete(o1);
wm1.has(o1);   // false

数据类型

基础类型:NumberStringBooleanundefinednullSymbolBigInt

复杂类型:ObjectFunction/Array/Date/RegExp/Math/Set/Map...)

类型检测

 typeof

基础(原始)类型

typeof 1 === "number" // true
typeof "a" === "string" // true
typeof true === "boolean" // true
typeof undefined === "undefined" // true
typeof Symbol() === "symbol" // true

引用类型

typeof null === "object" // true
typeof {} === "object" // true
typeof [] === "object" // true
typeof function () {} === "function" // true

用 typeof 判断引用类型就就很尴尬了,继续看别的方法吧

instanceof

判断是否出现在该类型原型链中的任何位置,判断引用类型可还行?一般判断一个变量是否属于某个对象的实例

console.log(null instanceof Object) // false
console.log({} instanceof Object) // true
console.log([] instanceof Object) // true
console.log(function(){} instanceof Object) // true

toString

let toString = Object.prototype.toString

console.log(toString.call(1)) // [object Number]
console.log(toString.call('1')) // [object String]
console.log(toString.call(true)) // [object Boolean]
console.log(toString.call(undefined)) // [object Undefined]
console.log(toString.call(null)) // [object Null]
console.log(toString.call({})) // [object Object]
console.log(toString.call([])) // [object Array]
console.log(toString.call(function(){})) // [object Function]
console.log(toString.call(new Date)) // [object Date]

这个还不错吧

其他判断

// constructor  判断是否是该类型直接继承者
let A = function(){}
let B = new A()
A.constructor === Object // false
A.constructor === Function // true
B.constructor === Function // false
B.constructor === A // true

// 判断数组
console.log(Array.isArray([])) // true
// 判断数字
function isNumber(num) {
  let reg = /^[0-9]+.?[0-9]*$/
  if (reg.test(num)) {
    return true
  }
  return false
}

//封装获取数据类型
function getType(obj){
    let type = typeof obj
    if(type !== 'object'){
        return type
    }
    return Object.prototype.toString.call(obj)
}

// 含隐式类型转换 继续往下看

// 判断数字
function isNumber(num) {  
    return num === +num
}
// 判断字符串
function isString(str) {  
    return str === str+""
}
// 判断布尔值
function isBoolean(bool) {  
    return bool === !!bool
}

类型转换

JS没有严格的数据类型,所以可以互相转换

  • 显示类型转换:Number()String()Boolean()
  • 隐式类型转换:四则运算,判断语句,Native 调用,JSON 方法

 显示转换

1. Number()

console.log(Number(1)) // 1
console.log(Number("1")) // 1
console.log(Number("1a")) // NaN
console.log(Number(true)) // 1
console.log(Number(undefined)) // NaN
console.log(Number(null)) // 0
console.log(Number({a:1})) // NaN 原因往下看

原始类型转换

  • 数字:转换后还是原来的值
  • 字符串:如果能被解析成数字,就得到数字,否则就是 NaN,空字符串为0
  • 布尔值:true 转为1,false 转为0
  • undefined: 转为 NaN
  • null:转为0 引用类型转换
let a = {a:1}
console.log(Number(a)) // NaN
// 原理
a.valueOf() // {a:1}
a.toString() // "[object Object]"
Number("[object Object]") // NaN

  • 先调用对象自身的 valueOf 方法,如果该方法返回原始类型的值(数值、字符串和布尔值),则直接对该值使用 Number 方法,不再继续
  • 如果 valueOf 方法返回复合类型的值,再调用对象自身的 toString 方法,如果 toString 方法返回原始类型的值,则对该值使用 Number 方法,不再继续
  • 如果 toString 方法返回的还是复合类型的值,则报错

2. String() 

console.log(String(1)) // "1"
console.log(String("1")) // "1"
console.log(String(true)) // "true"
console.log(String(undefined)) // "undefined"
console.log(String(null)) // "null"
console.log(String({b:1})) // "[object Object]" 原因往下看

原始类型转换

  • 数字:转换成相应字符串
  • 字符串:转换后还是原来的值
  • 布尔值:true 转为"true",false 转为"false"
  • undefined: 转为"undefined"
  • null:转为"null" 引用类型转换

let b = {b:1}
console.log(String(b)) // "[object Object]"
// 原理
b.toString() // "[object Object]"
// b.valueOf() 由于返回的不是复合类型所以没有调valueOf()
String("[object Object]") // "[object Object]"
  • 先调用 toString 方法,如果 toString 方法返回的是原始类型的值,则对该值使用 String 方法,不再继续
  • 如果 toString 方法返回的是复合类型的值,再调用 valueOf 方法,如果 valueOf 方法返回的是原始类型的值,则对该值使用 String 方法,不再继续
  • 如果 valueOf 方法返回的是复合类型的值,则报错

 3. Boolean()

console.log(Boolean(0)) // flase
console.log(Boolean(-0)) // flase
console.log(Boolean("")) // flase
console.log(Boolean(null)) // flase
console.log(Boolean(undefined)) // flase
console.log(Boolean(NaN)) // flase

原始类型转换

  • 0
  • -0
  • ""
  • null
  • undefined
  • NaN

以上统一转为false,其他一律为true 

隐式转换

// 四则运算  如把String隐式转换成Number
console.log(+'1' === 1) // true

// 判断语句  如把String隐式转为Boolean
if ('1') console.log(true) // true

// Native调用  如把Object隐式转为String
alert({a:1}) // "[object Object]"
console.log(([][[]]+[])[+!![]]+([]+{})[!+[]+!![]]) // "nb"

// JSON方法 如把String隐式转为Object
console.log(JSON.parse("{a:1}")) // {a:1}

几道隐式转换题

console.log( true+true  ) // 2                   解:true相加是用四则运算隐式转换Number 就是1+1
console.log(  1+{a:1}   ) // "1[object Object]"  解:上面说了Native调用{a:1}为"[object Object]"  数字1+字符串直接拼接
console.log(   []+[]    ) // ""                  解:String([]) =》 [].toString() = "" =》 ""+"" =》 ""
console.log(   []+{}    ) // "[object Object]"   解:"" + String({}) =》 "" + {}.toString() = "" + "[object Object]" =》 "[object Object]"
console.log(   {}+{}    ) // "[object Object][object Object]" 和上面同理

运算符优先级,图来自MDN

// 1. 展开运算符 ...
let obj1 = { a:1, b:{ c:3 } }
let obj2 = { ...obj1 }
obj1.a = 'a'
obj1.b.c = 'c'
console.log(obj1) // { a:'a', b:{ c:'c' } }
console.log(obj2) // { a:1, b:{ c:'c' } }

// 2. Object.assign() 把obj2合并到obj1
Object.assign(obj1, obj2)

// 3. 手写
function clone(target){
    let obj = {}
    for(let key in target){
        obj[key] = target[key]
    }
    return obj
}

// 4. 数组浅拷贝  用Array方法 concat()和slice()
let arr1 = [ 1,2,{ c:3 } ]
let arr2 = arr1.concat()
let arr3 = arr1.slice()

深拷贝

拷贝栈也拷贝堆,重新开僻一块内存

1. JSON.parse(JSON.stringify())

let obj1 = { a:1, b:{ c:3 } }
let obj2 = JSON.parse(JSON.stringify(obj1))
obj1.a = 'a'
obj1.b.c = 'c'
console.log(obj1) // { a:'a', b:{ c:'c' } }
console.log(obj2) // { a:1, b:{ c:3 } }

该方法可以应对大部分应用场景,但是也有很大缺陷,比如拷贝其他引用类型,拷贝函数,循环引用等情况

2. 手写递归

原理就是递归遍历对象/数组,直到里面全是基本类型为止再复制

需要注意的是 属性引用了自身的情况,就会造成循环引用,导致栈溢出

解决循环引用 可以额外开僻一个存储空间,存储当前对象和拷贝对象的关系,当需要拷贝对象时,先去存储空间找,有木有这个拷贝对象,如果有就直接返回,如果没有就就继续拷贝,这就解决了

这个存储空间可以存储成key-value的形式,且key可以是引用类型,选用Map这种数据结构。检查map中有木有克隆过的对象,有就直接直接返回,没有就将当前对象作为key,克隆对象作为value存储,继续克隆

function clone(target, map = new Map()){
    if (typeof target === 'object') { // 引用类型才继续深拷贝
        let obj = Array.isArray(target) ? [] : {} // 考虑数组
        //防止循环引用
        if (map.get(target)) {
            return map.get(target) // 有拷贝记录就直接返回
        }
        map.set(target,obj) // 没有就存储拷贝记录
        for (let key in target) {
            obj[key] = clone(target[key]) // 递归
        }
        return obj
    } else {
        return target
    }
}

优化版

  • WeakMap替代Map,上面说了WeakMap是弱引用,Map是强引用
  • 选择性能更好的循环方式

for in 每次迭代操作会同时搜索实例和原型属性,会产生更多的开销,所以用 while

// 用while来实现一个通用的forEach遍历
function forEach(array, iteratee) {
    let index = -1;
    const length = array.length;
    while (++index < length) {
        iteratee(array[index], index);
    }
    return array;
}
// WeakMap 对象是键/值对集合,键必须是对象,而且是弱引用的,值可以是任意的
function clone(target, map = new WeakMap()){
    // 引用类型才继续深拷贝 
    if (target instanceof Object) {
        const isArray = Array.isArray(target)
        // 克隆对象和数组类型
        let cloneTarget = isArray ? [] : {} 
        
        // 防止循环引用
        if (map.get(target)) {
            // 有拷贝记录就直接返回
            return map.get(target) 
        }
        // 没有就存储拷贝记录
        map.set(target,cloneTarget) 
        
        // 是对象就拿出同级的键集合  返回是数组格式
        const keys = isArray ? undefined : Object.keys(target)
        // value是对象的key或者数组的值 key是下标 
        forEach(keys || target, (value, key) => { 
            if (keys) {
                // 是对象就把下标换成value
                key = value 
            }
            // 递归
            cloneTarget[key] = clone(target[key], map) 
        })
        return cloneTarget
    } else {
        return target
    }
}

new

new 干了什么?

  • 创建一个独立内存空间的空对象
  • 把这个对象的构造原型(__proto__)指向函数的原型对象prototype,并绑定this
  • 执行构造函数里的代码
  • 如果构造函数有return就返回return的值,如果没有就自动返回空对象也就是this

有一种情况如下,就很坑,所以构造函数里面最好不要返回对象,返回基本类型不影响

function Person(name){
    this.name = name
    console.log(this) // { name: '沐华' }
    return { age: 18 }
}
const p = new Person('沐华')
console.log(p) // { age: 18 }

原型

猜你喜欢

转载自blog.csdn.net/w199809w/article/details/130134315
今日推荐