var let const 区别
var
es5 之前只有var 会有声明提升问题 也就是声明会提前到它所在的作用域顶部 且允许重复声明 var 痛点:
- 变量声明提升 在声明前可以访问,不报错,值为undefined,违反编码直觉
- 缺乏块级作用域 if块和for循环中用var声明的变量会泄露到外部
let const
es6中用let const 来解决这两个问题
- 会和花括号{}形成块级作用域
- 不允许重复声明
- 声明前无法访问 声明前想要使用会抛出错误 当块级作用域中提前访问变量出现暂时性死区,即使外部有声明这个变量但是依旧无法访问
- 在全局作用域声明不会被挂载在window上但是var会
const 声明的原始类型无法修改值,但是声明的引用类型无法修改引用地址,比如说指向一个新的对象。可以修改内部的值,例如修改对象的值,或者数组的内容
const 声明的变量必须要有值
js中的数据类型
原始类型
在栈中直接存储数据的值 赋值是值的拷贝
引用类型
在堆中通过地址索引存放内容, 在栈中存放地址 赋值的地址的拷贝
js
function modify(obj){
obj.name = 'Modified'
obj = {
name:'new obj'
}
}
let myObj ={ name:'old obj'}
modify(myObj)
console.log(myObj.name)
调用modify(myObj)的时候就就相当于让形参obj和myObj指向同一片地址 ,这时候修改obj.name myObj会受到牵连,obj = { name:'new obj' }这里是让obj指向另一片地址,也就是和myObj断开了连接,后续修改不相干了,因此最后输出Modified
===
引用类型的全等判断是比较地址 原始类型的全等判断是比较值
数据类型
js中数据类型分为两大类
- 原始类型 number string boolean null undefined bigint symbol
- 引用(对象)类型 array object function map set Date RegExp(正则)等等
typeof
typeof可以精准的判断所有原始类型除了null、 对引用类型只能判断function 除此之外都是object
typeof null 判断出来是object
在js中判断数据类型是通过类型标签来实现的,类型标签都是由二进制数构成 对象的类型标签是000 然后null的转为二进制是全0 因此被判断成object
那我们该怎么判断null呢? 用null === null
该如何判断引用类型
instance of 可以判断所有的引用类型,但是不能判断原始类型,他的原理是原型链 判断数组 Array.isArray()
通杀所有类型方式
Object.prototype.toString.call()
深浅拷贝
拷贝只针对引用类型
浅拷贝shallowCopy
创建一个新对象 复制原对象的第一层属性,如果是原始类型就复制值,如果是引用类型就复制引用地址
- object.assign({},originalObj)
-
...originalArray\],{...originalObj}
- object.create
深拷贝deepCopy
创建一个新对象 复制原对象的所有层级属性,新旧对象完全独立
- JSON.parse(JSON.stringify()) 会忽略undefined symbol function date对象变成字符串 有循环引用直接报错
- structuredClone() 最推荐这个
- 复杂场景 要拷贝函数用lodash.cloneDeep 库
手写深浅拷贝
js
fucntion shallowClone(obj){
if(obj === null || typeof obj !== 'object') return obj
const copy = Array.isArray(obj)?[]:{}
for(let key in obj){
if(obj.hasOwnProperty(key)){
copy[key] = obj[key]
}
}
return copy
}
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj
if (cache.has(obj)) return cache.get(obj) //如果是循环引用就直接返回值
const copy = Array.isArray(obj) ? [] : {}
cache.set(obj, copy) //避免循环引用
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key], cache) //递归复制
}
}
return copy
}
深拷贝要注意循环引用的情况
为什么要深拷贝?
避免修改副本时,污染原始数据 创建一份独立的数据快照
数组身上的方法
map
转换数组,创建新数组,保持原数组不变,返回新数组
filter
筛选数组,创建新数组,保持原数组不变,返回包含满足条件的元素的新数组
reduce
汇总累加数组 接受两个参数,一个回调函数,第二个参数是回调函数中的第一个参数,遍历数组,将每次返回的值作为下一次回调的第一个参数,进行累加
splice 和 slice
splice改变原数组,返回被删除的数组 接受三个参数 第一个是删除/增加的位置索引,第二个参数是删除的个数,第三个是增加的元素 slice不改变原数组,返回数组的浅拷贝片段,接受两个参数,第一个是起始位置是,第二个是结束位置
map 和 forEach 区别
- map 返回新数组 forEach 返回 undefined
- 如果要对数据进行转换就用 map forEach 用于执行无返回值的操作 比如说仅仅只要获取值
- map 可以链式调用 forEach 不可以
reduce 里面的 initialValue 提供与否的区别
如果给了这个参数就由这个参数开始累加,从数组的第一个元素开始遍历 index:0,如果不给的话初始值就默认是数组的第一个元素,然后从数组第二个元素开始遍历也就是 index:1
开始不给初始值对空数组会直接报错
修改原数组的方法
push pop shift unshift splice sort reverse
不修改原数组 返回新副本
map filter reduce slice concat
对象属性的遍历
对象属性
自有属性和原型链属性 可枚举属性和不可枚举属性 字符串属性和 symbol 属性
for in
遍历自有以及原型链上的可枚举字符串属性 因此必须搭配 hasOwnProperty 来使用,只拿自有属性来过滤原型链属性
Object.keys()
返回自有的可枚举字符串属性 官方的 for in + hasOwnProperty且效率更高 可以直接拿到干净的属性列表
Object.getOwnPropertyNames()
返回所有字符串属性
Object.getOwnPropertySymbols()
返回所有 Symbol 属性
Reflect.ownKeys()
返回所有属性 Reflect.ownKeys(obj) = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))