前言
本篇章次要介绍了援用类型的特点,诸如正则表达式、工夫类型、根本包装类、Object、数组等,至于function局部,内容较多,将另起篇章。
面试答复
1.援用类型:援用类型有array、object、function。援用类型属于堆内存,占用的空间不固定,保留和复制的是指针。援用类型罕用instanceof去判断,如果自身不晓得数据属于什么类型的话,就用object.prototype.toString.call办法去判断,不论是instanceof还是call办法,他们实质上都是通过原型链判断。
2.数组去重:最罕用的是利用Set这种数据结构,搭配Array.from或者扩大运算符来实现。至于其余形式,不论是利用indexOf、includes、sort(sort排序后雷同的元素会相邻),首先都会有一层遍历。
3.深浅拷贝:深浅拷贝的区别在于,浅拷贝只复制指针,新旧对象仍应用同一块内存。而深拷贝会创立一个截然不同的对象,开拓新的内存,因而批改新对象不会影响原对象。简略点,就用JSON.parse包裹着JSON.stringify,这种形式算深拷贝,因为它开拓了新的内存空间,只不过JSON.stringify会失落一些数据,比方undefined、function。精确点,就须要采取递归的形式去深拷贝,判断传入的数据是不是object,如果不是就抛出去,如果是那么就持续递归。
4.new关键字:new关键字先是创立新的对象,而后继承传入的函数原型,批改this指向并且执行构造函数,最初返回新的对象。
知识点
援用类型都是堆(heap) ,有两个特色:1. 保留和复制都是指针。2.占用空间不固定(大小不定),也不会主动开释,堆内存中。
1.RegExp
根本规定
对于RegExp对象还是常常会应用到,比方限度input输入框(校验邮箱是否合乎)、匹配变量值做if判断等等。RegExp类型既能够通过字面量申明的形式创立,也能够通过new的形式创立;
字面量申明:/pattern/attributes ,例:let re1 = /ABC/g
new申明:new RegExp(pattern, attributes),例let regex=new RegExp(‘/abc/’,g)
pattern既能够是正则表达式,也能够是字符串,如果是字符串,则如下:
let reg = /hello/g
let test = reg.test("hello World")
console.log(test) // true
attributes为可选的字符串 ,含属性 “g”、”i” 和 “m”,别离用于指定全局匹配、不辨别大小写的匹配和多行匹配
上面着重梳理几条规定,用来可能根本的辨认正则表达式,具体属性能够参考:RegExp根底规定
1./ xxx /g ,所有的正则表达式均写在/ /内,反斜杠代表本义,如\d,示意数字
2.^
示意行的结尾,^\d
示意必须以数字结尾
$
示意行的完结,\d$
示意必须以数字完结
有头有尾则必须完全符合规定才可匹配,如^\d{3}$
则示意,间断三个数字才可匹配。
3.[]示意可能匹配的范畴,匹配的是单个字符
4.如果波及特殊字符如'-'
,在正则表达式中,要用'\-'
本义,如,/^\d{3}\-\d{3,8}$/
。
5.n{X,Y}示意至多呈现X个间断n ,至少呈现Y个间断n时,匹配。Y可不写。闭区间,含X,Y
6.*等于{0,} 即大于等于0次 ;+代表{1,} 即大于等于1次;?代表{0,1} 即0次或1次
7.用()
示意的就是要提取的分组。比方:^(\d{3})\-(\d{3,8})$
8.取反:[^abc]
,查找任何不在方括号之间的字符。
依据下面8条规定来辨认一下下述含意:
let re = /^\d{3}\-\d{3,8}[0-9][a-z]$/;
去掉//,主体为^\d{3}\-\d{3,8}$
^,示意开始
\d{3},示意间断三个数字
\-,示意特殊字符-
\d{3,8},示意至多间断3个数字至少间断8个数字
[0-9],示意这个地位的字符必须为0-9
[a-z],示意这个地位的字符必须为a-z
$,示意完结
罕用办法
RegExp 对象有 4 个办法:test()、exec() 、compile()、match()
。
test():test() 办法检索字符串中的指定值。返回值是 true 或 false。
match():match()办法返回值是合乎规定的值(数组)。
exec():exec() 办法检索字符串中的指定值。返回值是被找到的值。如果没有发现匹配,则返回 null
compile():compile()办法用于扭转 RegExp。
2.Date
定义
Date获取的工夫为本地工夫,即电脑或手机上的设施工夫,如果你设施工夫不精确,那么你获取的工夫同样也会不精确;如若波及时区问题,能够应用UTC工夫 (零时区) 。
根底API
//获取以后工夫
let b = new Date()
//获取年份
b.getFullYear()
//获取月份
b.getMonth()+1
//获取天
b.getDate()
//获取小时
b.getHours()
//获取分钟
b.getMinutes()
//获取秒数
b.getSeconds()
//获取毫秒
b.getMilliseconds()
//获取星期几
b.getDay()
//获取字符串模式
b.toString()
//字符串模式仅工夫局部
b.toTimeString()
//字符串模式仅日期局部
b.toDateString()
//获取工夫戳
b.getTime()(当地时区)或者b.valueOf()
//获取UTC工夫
b.toUTCString()
//获取指定日期的工夫类型
new Date(yyyy,mth,dd,hh,mm,ss); mth为0-11
new Date('2017-05-24 12:30:23')
//日期加减,年月日时分秒同理,可查问相干API
let dt = new Date('2021-11-10');
dt.setDate(dt.getDate() + 10);
console.log(dt.toString()); // Tue Nov 20 2021 08:00:00 GMT+0800 (中国规范工夫)
利用场景
1.针对要求高精确性的工夫,举个例子:
假如:本地工夫、服务器工夫不统一,而这个假如产生的概率很大。
需要:一个案件须要点击拨打电话后才可提交案件
操作:点击拨打按钮记录的是本地工夫(用来展现);提交案件记录的是服务器的工夫(后端须要记录案件提交的工夫);而这时候工夫不统一会带来许多剖析上的麻烦。比方你明明12:30:00(本地工夫)打了电话,提交案件的工夫却是12:00:00(服务器工夫)。实践上提交案件的工夫应该晚于拨打工夫,却因为工夫不统一,导致数据分析有异样。
解决办法:通过调用接口获取服务器工夫,对立工夫上的治理,防止造成数据分析的麻烦。
经验总结:如果波及到工夫问题,特地是对工夫准确要求较高的需要时,须要思考工夫获取起源的对立。
2.针对工夫格局的对立与转换
问题原因:不同的浏览器对于new Date()的解析反对是不同的,比方ios就只反对”2021/12/12 12:21:21″,而局部浏览器则只反对”2021-12-12 12:21:21″。而有时候需要对工夫格局有着另外的要求,也须要对工夫进行对立的解决。
经验总结:
首先,波及到工夫问题,首先要思考工夫格局能不能被辨认兼容
其次,须要与后端对立好工夫格局,防止因为工夫格局产生的报错,诸如显示、转换等,举荐办法是前后端传值对立应用工夫戳,工夫格局由前端进行转换。
最初,波及工夫格局的转换,常见的便是new Date() 、elementUI中的dateTimepicker,而工夫格局的转换我这边举荐插件moment.js或者dayjs或者自行封装一个繁难的工夫处理函数。
3.根本包装类型
实质
为了便于操作根本类型值,ECMAScript 还提供了3 个非凡的援用类型:Boolean、Number和String。而boolean、number、string根本类型之所以可能间接操作是因为在调用办法的过程中,默默进行根本包装类型的解决,如下:
let str1 = 'hello'; //string 根本类型
let str2 = str.indexOf('o'); //在执行到这一句的时候 后盾会主动实现以下动作 :
(
var _str = new String('hello') // 过对应的包装对象创立出一个和根本类型值雷同的对象
var s2 = _str.indexOf('o'); // 通过包装对象进行办法的调用,并赋值给s2
_str = null; // 销毁包装对象
)
相干知识点
装箱:根底类型–>援用类型
string:new String()
boolean:new Boolean()
number:new Number()
拆箱:援用类型–>根底类型
let numObj = new Number(123)
let strObj = new String('zxc')
let booObj = new Boolean(true)
console.log( typeof numObj ) //object
console.log( typeof strObj ) //object
console.log( typeof booObj ) //object
//拆箱
console.log( typeof numObj.valueOf() ); //number
console.log( typeof strObj.valueOf() ); //string
console.log( typeof booObj.valueOf() ); //boolean
4.Object
属性的加强写法
const name ='chiji'
const age = '18'
const obj = {
name, //name:name
age //age:age
}
console.log(obj.name) //chiji
console.log(obj.age) //18
JSON的了解
JSON是一种轻量级的数据交换格局,次要是为了跨平台交换数据用的,因而他的格局要求也绝对标准严格,比方JSON的属性名必须有双引号,如果值是字符串,也必须是双引号。因为在申请接口中应用的JSON格局,所以对于前端代码中的入参根本都是通过序列化的形式进行入参的解决,如:
let o = {a:1}
let ojson = JSON.stringify(o) //'{"a":1}' string类型
let oobj = JSON.parse(ojson) //{a: 1} object类型
遍历办法
let obj= {
a:'1',
b:2,
c:false
}
Object.keys
Object.keys(obj)
//['a', 'b', 'c']
Object.values
const obj = {
100:'a',
2:'b',
7:'c'
};
Object.values(obj)
//['b','c','a']
PS:如果属性名为数值的属性,是依照数值大小,从小到大遍历的
Object.entries(obj)
Object.entries(obj)
//[['a', '1'],['b', 2],['c', false]]
for…in:数组也能够应用,然而该办法会遍历对象的整个原型链,性能十分差,且遍历程序不确定,不举荐应用
for(let key in obj){
console.log(key + '---' + obj[key])
}
//a---1
//b---2
//c---false
Object.getOwnPropertyNames(obj):
Object.getOwnPropertyNames(obj)
// ['a', 'b', 'c']
Reflect.ownKeys(obj)
Reflect.ownKeys(obj)
// ['a', 'b', 'c']
对象转数组
Object.keys({name:'张三',age:14}) //['name','age']
Object.values({name:'张三',age:14}) //['张三',14]
Object.entries({name:'张三',age:14}) //[[name,'张三'],[age,14]]
对象合并
对象:
let obj1 = {
a:1,
b:'zxp',
c:function(){},
d:{
abc:'123',
qwe:{}
}
}
let obj2 = { a: 5 };
---------合并--------
let obj3 = {...obj1, ...obj2};
//如果有反复的key,则前面的会将后面的值笼罩掉
//Object.assign(target, source1, source2,...);
//let obj3 = Object.assign({},obj1, obj2);
//如果不应用{}作为target,合并后obj1的数据也会批改
//{
a:5,
b:'zxp',
c:function(){},
d:{
abc:'123',
qwe:{}
}
}
5.Array
与Object的区别
1.数组有序,Object无序
2.数组的元素能够没有属性名(然而有索引),对象的元素必须有值
3.数组只能用整数作为数组元素的索引,而不能用字符串,且数组元素的应用只能通过方括号(arr[0])来获取
4.数组的空元素empty示意空位, 它不是一种数据类型, 而是因为人为批改arr.length 或者写入时多写了逗号造成的。empty和undefined在操作数组时的次要区别在于:应用数组的forEach()办法遍历时会主动疏忽空位, 而应用for循环则会将empty转换为undefined并遍历。
let arr = [1,2,3,4,5]
delete arr[3]
console.log(arr)
Tip:应用对象或数组的办法前肯定要先判断其是否存在,因为数组常常会连带办法应用,比方arr.indexOf(‘123’),此时如果arr不存在,那么程序会间接报错。
不批改原数组的办法
1、2:判断数组中是否蕴含一个元素
3、4:找出第一个符合条件的数组成员
5:判断数组是否为空,因为数组是对象,因而存在援用地址的问题,所以不能应用 arr === [] 来判断数组是否为空
6、7:合并数组能够应用,不过6、7都属于浅拷贝,即仅实用于对不蕴含援用对象的一维数组的拷贝,就是对象不是那种多层嵌套的
8:复制数组,不过8属于浅拷贝,即仅实用于对不蕴含援用对象的一维数组的拷贝,就是对象不是那种多层嵌套的
9:适宜用于将数组元素拼接后转换成字符串,但如果元素为undefined或null,它会被转换为空字符串。
let arr = [1,'2',false,null,{a:1}]
1.includes(searchvalue, start):searchvalue为查找元素,start为查找起始index
arr.includes(1)
//true
2.indexOf:同1
arr.indexOf(1)>-1
//true
3.find(function(currentValue, index, arr)):currentValue为以后元素,index为元素索引,arr为以后数组
arr.find((n)=>n>0)
//1 1为值
4.findIndex():同3
arr.findIndex(function(value){
return value >0
})
//0 0为索引
5.length
arr.length ===0
//false
6.Spread扩大运算符(...):
const arr1 = [11,22,33]
const arr2=[...arr,...arr1]
console.log(arr2)
//[1, '2', false, null, {a:1}, 11, 22, 33]
7.concat(arr1,arr2)
let combine = arr.concat([11,22,33])
//[1, '2', false, null, {a:1}, 11, 22, 33]
8.slice(start,end):返回一个新数组,start为起始index,end为完结index
let copy = arr.slice()
copy
// [1,'2',false,null,{a:1}]
9.join(str)
let str =arr.join(',')
str
//'1,2,false,,[object Object]'
批改原数组的办法
let arr = [1,'2',false,{a:1},null]
1.push(item): 从数组最初面增加元素(一个或多个),返回值为增加完后的数组的长度
arr.push(666) // 6
arr // [1,'2',false,{a:1},null,666]
2.unshift(unshift):从后面增加元素, 返回值是增加完后的数组的长度
arr.unshift(666) // 6
arr //[666,1,'2',false,{a:1},null]
3.pop():从数组最初面删除一个元素,返回值是删除的元素
arr.pop() // null
arr //[1,'2',false,{a:1}]
4.shift():从后面删除元素,只能删除一个,返回值是删除的元素
arr.shift() // 1
arr //['2',false,{a:1},null]
5.reverse():颠倒数组中元素的排列程序
arr.reverse();
arr // [null, {a:1}, false,'2',1]
6.sort():间接应用sort()办法,默认的排序办法会将元素转换为字符串,而后比拟字符串中字符的UTF-16的编码程序来进行排序,所以这里接管一个函数,返回值是比拟这两个数的绝对程序的值
var arr = [3,4,8,5,1,6,7]
var brr = arr.sort((a,b)=>a-b)
//[1,3,4,5,6,7,8]
7.splice(index,howmany,item1,...,item2):index为起始地位,howmany为解决的数据数量是个number,item1为新元素。若是删除,则返回被删除元素组成的数组,不传值默认全删
let item = arr.splice(0, 3);
console.log(item);//[1, '2', false]
console.log(arr); [{a:1}, null]
8.fill(value,start,end):将数组中指定区间的所有元素的值,都替换成某个固定的值,并返回这个数组。value是要填充的数字,start是起始索引(数组下标),end是完结索引(数组下标),含头不含尾,如果start/end 为正数,则理论为start+length/end+length
arr.fill(9);// [9, 9, 9, 9, 9]
arr.fill(9,2);// [1, '2', 9, 9, 9]
arr.fill(9,1,3);//[1, 9, 9, {…}, null]
arr.fill(9,3,3);// [1, '2', false, {…}, null]
arr.fill(9,-3,-1);// [1, '2', 9, 9, null] ,能够了解为从数组倒数第三个开始,到数组倒数第一个完结(非数组下标)
9.copyWithin(target,start,end):在以后数组外部,将指定地位的元素复制到其余地位,并返回这个数组。target,索引从该地位开始替换数组项(蕴含)。start,索引从该地位开始读取数组项,默认为0.如果为负值,则从右往左读,end,索引到该地位进行读取的数组项,默认是Array.length,如果是负值,示意倒数,含头不含尾。
let arr = [1,'2',false,{a:1},null,3,3,3,3,3,3,3,3,3,3]
arr.copyWithin(3,0,4);
// 0,4 读取的是 1,'2',false,{a:1} ,只用这四个值顺次替换1遍
// 3 从数组下标为3的地位开始替换(蕴含)
// [1, '2', false, 1, '2', false, {…}, 3, 3, 3, 3, 3, 3, 3, 3]
遍历办法
let arr = [1,'2',false,{a:1},null]
1.for...of:
for (const value of arr) {
console.log(value);
}
//1
//'2'
//false
//{a: 1}
//null
2.for...in:尽量别用,Object能用的办法Arry都能用,不过输入的值不肯定是你要的
for(let key in arr){
console.log(arr[key])
}
//1
//'2'
//false
//{a: 1}
//null
3.array.forEach():
arr.forEach((val,index)=>{
console.log('val:'+val+';index:'+index)
})
//val:1;index:0
//val:2;index:1
//val:false;index:2
//val:[object Object];index:3
//val:null;index:4
4.arr.map():
arr.map((item,index)=>{
console.log('index',index)
console.log('item',item)
})
// index 0 item 1
// index 1 item 2
// index 2 item false
// index 3 item {a: 1}
// index 4 item null
PS:forEach()和map()的区别
1.forEach()办法不会返回执行后果,而是undefined;而map()办法会失去一个新的数组并返回
2.因为map()底层做了深度优化,同样的一组数组,map()的执行速度优于forEach()
5.arr.every((item,index)=>{
//这里写判断,须要每一项都合乎,最初才返回true,否则返回false
return item >100
})
6.arr.some((item,index)=>{
//这里写判断,只有有一项都合乎,便true,否则返回false
return item >100
})
7.arr.filter((item,index)=>{
//符合条件的,将作为数组的元素,最初返回一个数组
return typeof item==='number'
})
//[1]
8.arr.reduce((prev,cur,index,arr)=>{
return prev + cur
},init)
//arr 示意原数组;
//init 示意初始值
//prev 示意上一次调用回调时的返回值,或者初始值 init;
//cur 示意以后正在解决的数组元素;
//index 示意以后正在解决的数组元素的索引,若提供 init 值,则索引为0,否则索引为1;
如果没有init,输入如下:
'null[object Object]false21'
9.arr.reduceRight((prev,cur,index,arr)=>{
return prev + cur
},init)
//reduceRight()办法的性能和reduce()性能是一样的,不同的是reduceRight()从数组的开端向前执行
数组合并
数组:
let arr1 = [false,'',1,'5']
let arr2 = [9,'8',true,function(){}]
let arr3 = arr1.concat(arr2);
//let arr3 = [...arr1,...arr2)];
//arr1.push(...arr2); 会扭转原数组
//[false,'',1,'5',9,'8',true,function(){}]
数组去重
let obj = {
a:1,
b:'zxp',
c:function(){},
d:{
abc:'123',
qwe:{}
}
}
let arr = [1,1,1,'chiji',obj,obj,'c','c']
Set对象+Spread扩大运算符
[...new Set(arr)] //[1,'chiji',obj,'c']
毛病:其实也不能算是缺点,只是满足不了需要
如果数组如下,
let arr = [1,1,1,'chiji',obj,obj,{a:1},{a:1},'c','c',{}]
[...new Set(arr)] //[1,'chiji',obj,{a:1},{a:1},'c',{}]
因为实质上两个{a:1}的内存存储地址不一样,所以不会被去重,而往往在需要上,这种数据是须要咱们去重的,空对象也是无奈被删除的。
PS:这边能够想想,如何去除{}、[]、{a:1}等状况,以及object的去重,搞个公共办法进去
数组扁平化
//1.Array.flat(n)是ES10扁平数组的api,n示意维度,n值为Infinity时维度为无限大
[1,[2,3]].flat(1) //[1,2,3]
[1,[2,3,[4,5]]].flat(2) //[1,2,3,4,5]
[1,[2,3,[4,5]]].toString() //'1,2,3,4,5'
[1[2,3,[4,5[...]].flat(Infinity) //[1,2,3,4...n]
//2.递归
let arr = [1,[2,3],[4,[5,6]],[7,[8,[9,10]]]]
function toOne(array){
let newArr = []
let deepArr = (arr) =>{
if(typeof arr === 'object'){
arr.map(item=>{
let itemType = Object.prototype.toString.call(item)
if(itemType === '[object Array]'){
deepArr(item)
}else{
newArr.push(item)
}
})
}else{
newArr.push(arr)
}
}
deepArr(array)
return newArr
}
toOne(arr) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
利用
1.关键字
前端关键字有:delete、switch、case、default、break(跳出循环,map办法中不可用)、for、in、continue(跳出本次循环)、while、if、else、return(进行办法的执行)、try、catch、this、do、with、finally、throw、let、var、function、instanceof、typeof、new 等,这边筛选局部进行解释。
delete关键字
let arr = [1,2,3,4,5,6]
let obj= {
"a":"1",
"b":"2",
"c":"3",
"d":"4",
}
delete arr[0]
console.log(arr) //[empty, 2, 3, 4, 5, 6]
delete obj.b
console.log(obj) // {a: '1', c: '1', d: '1'}
new关键字
接下来形容一些new操作符的的原理、过程及实现:
new操作符的作用是通过构造函数来创立一个实例对象,那么new操作符做的事包含:
1.创立新的对象
2.继承传入函数的原型
3.批改this指向并执行构造函数
4.返回新对象
实现:
function _new(Fn, ...args) {
let obj = {}; //创立一个空对象
obj.__proto__ = Fn.prototype; //继承函数原型就是,使实例对象的__proto__(原型)= 构造函数的prototype(原型)
//下面两步也能够合并为:let obj=Object.create(Fn.prototype);
let res = Fn.call(obj, ...args); //批改this指向并执行构造函数
//下面这步也能够写为:let res = Fn.apply(obj, args); 区别是一个参数为数组,一个参数为xx,xx,xx的模式
return res instanceof Object ? res : obj; //如果new作用的指标是根底类型,则返回obj根底类型,如果是援用类型object,则返回res
}
验证:
// 构造函数
function Person(name, age){
this.name = name;
this.age = age;
this.test = {
a:'test',
b:function(){}
}
return this.test //如果构造函数有return,且为Object,则p为这个,否则p为obj
}
let p = _new(Person, 'Tom', 20)
console.log(p)
//{a: 'test', b: ƒ}
PS:至于为什么不能写成let p = _new Person('Tom', 20)的模式,是因为_new是咱们自定义的办法,须要以办法的模式调用;而let p = new Person(xxxx)之所以能够如此应用,是因为new是ECMA规定的内置语法,如果你非要写成_new Person()模式,就相当于新增了一个内置语法,等同于ES5降级到ES6
2.解构赋值
数组:
let [a, [[b], c]] = [1, [[2], 3]];
// a = 1 b = 2 c = 3
let [a = 1, b] = [];
// a = 1, b = undefined
let [a, ...b] = [1, 2, 3];
//a = 1 b = [2, 3]
let [a = 3, b = a] = [1, 2];
// a = 1, b = 2
对象:
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello' y = 'world'
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10 b = 20 rest = {c: 30, d: 40}
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;
3.深浅拷贝
深浅拷贝的区别在于,浅拷贝只复制指向某个对象的指针,而不复制对象自身,新旧对象还是共享同一块内存。但深拷贝会另外发明一个截然不同的对象,新对象跟原对象不共享内存,批改新对象不会改到原对象。
具体表现即,深拷贝的数据与被拷贝对象齐全没有关系,不论被拷贝对象如何改变,都不会影响深拷贝的数据;而浅拷贝的数据如果内层不嵌套,则与深拷贝的体现统一,如果内层嵌套,则被拷贝对象嵌套局部的数据批改会使拷贝对象同步批改。
数据:
let obj = {
a:1,
b:'zxp',
c:function(){},
d:{
abc:'123',
qwe:{}
}
}
let target = { a: 1, b: 1 };
let arr = [1,'chiji',obj,'c']
浅拷贝:
//Object.assign(target,source):将源对象(source)的所有可枚举属性,复制到指标对象(target),只能拷贝一层。
Object.assign(target, obj);
//数组
let copy = Array.from(new Set(arr)); // 留神:该办法不能用在原数组里有反复项,会被去重
//let copy = [...arr];
//let copy = arr.slice();
//let copy = Array.of (...arr);
//let copy = new Array(...arr);
//let copy = arr.concat();//concat() 办法用于连贯两个或多个数组
arr[0] = 2
//arr批改,copy不变
arr[2]['d']['abc'] = 987
//arr批改,copy批改,obj批改
深拷贝:
let copy = JSON.parse(JSON.stringify(arr))
//简便状况下能够应用该办法,然而该办法存在缺点:null,NaN, undefined, Infinity,Date对象等数据类型在转化过程中会呈现失落、异样等状况,至于具体每个异常情况本人去搜寻一下
arr[0] = 2
//arr批改,copy不变
arr[2]['d']['abc'] = 987
//arr批改,copy不变
递归深拷贝:
function deepCopy(data){
let target
if(!data && typeof data !== 'object'){
//如果是根底类型,那么就间接返回
target = data
}else{
target = data instanceof Array ? [] : {}
//遍历数据,将key输入为数组,不便进行遍历
Object.keys(data).forEach((key)=>{
if(data[key] && typeof data[key] === 'object'){
//如果数据的值为对象,那么递归持续遍历上来
target[key] = deepCopy(data[key])
}else{
target[key] = data[key]
}
})
}
return target
}
4.类型判断
let bool = true
let num = 1
let str = 'abc'
let und= undefined
let nul = null
let arr = [1,2,3,4]
let obj = {name:'chiji',age:25}
let fun = function(){console.log('hello')}
let sym = Symbol()
typeof:typeof能够辨认出根本类型boolean,number,undefined,string,symbol,然而不能辨认援用数据类型,会把null、array、object对立归为object,然而能够辨认出function。
console.log(typeof bool) //boolean
console.log(typeof num) //number
console.log(typeof str) //string
console.log(typeof und) //undefined
console.log(typeof sym) //symbol
console.log(typeof fun) //function
console.log(typeof nul) //object
console.log(typeof arr) //object
console.log(typeof obj) //object
instanceof:能够辨认援用类型,如array、object、function。
因为js的继承都是采纳原型链来继承的,因而应用instanceof还能够检测出new申明的类型的多层继承关系。
console.log(bool instanceof Boolean) // false
console.log(num instanceof Number) // false
console.log(str instanceof String) // false
console.log(und instanceof Object) // false
console.log(nul instanceof Object) // false
//此处的根本类型均非通过根本包装类型new生成,因而原型链上找不到原型返回false
console.log(arr instanceof Array) // true
console.log(obj instanceof Object) // true
console.log(fun instanceof Function) // true
console.log(sym instanceof Symbol) // false
Object.prototype.toString.call:此办法能够绝对较全的判断js的数据类型,然而比拟长,嫌麻烦的话能够依据场景抉择上述办法;如果检测的是个根本类型,实际上会主动转成对应的援用类型,比方string会转成new String(),但对于自定义类型,就只能用 instanceof 了 。
console.log(Object.prototype.toString.call(bool)) //[object Boolean]
console.log(Object.prototype.toString.call(num)) //[object Number]
console.log(Object.prototype.toString.call(str)) //[object String]
console.log(Object.prototype.toString.call(und)) //[object Undefined]
console.log(Object.prototype.toString.call(nul)) //[object Null]
console.log(Object.prototype.toString.call(sym)) //[object Symbol]
console.log(Object.prototype.toString.call(arr)) //[object Array]
console.log(Object.prototype.toString.call(obj)) //[object Object]
console.log(Object.prototype.toString.call(fun)) //[object Function]
Array.isArray:此办法为ES5新增,用于判断JS数组类型
console.log(Array.isArray(arr)) // true
5.intanceof操作符的原理及实现
MDN定义:instanceof运算符用于测试构造函数的prototype属性是否呈现在对象原型链中的任何地位
了解:原理也就是后面了解的原型链知识点。这里的主体一个是构造函数的原型,一个是实例对象的原型。如果构造函数的原型在实例对象的原型上,则返回true,反之,false。
left instanceof right //实际上left等同于实例对象,right等同于构造函数
function _instanceof (left, right) {
let l = left.__proto__; //实例对象的原型
while (l) {
if (right.prototype === l) {
return true;
}
l = l.__proto__;
}
return false;
}
const obj = new Object({a:1})
const res = _instanceof(obj,Object) //此处用法起因与new的PS统一
console.log(res)
最初
走过路过,不要错过,点赞、珍藏、评论三连~