JS基础
闭包
(1)什么是闭包
能读取其他函数内部变量的函数。子级一级一级向上查找,父级不能获取子级。
(2)闭包使用场景
函数柯里化、防止变量被污染。
(3)使用不当闭包会造成什么问题?
使用不当会造面内在内泄;内泄:访问不到的变量,占用内存;
注)这个谣言来源于:IE中有bug,IE 中使用完闭包,回收不了闭包里面引用的变量
(4)总结
(1)比普通函数占用内存多。
(2)闭包不使用时,要及时释放。
(3)将引用内层函数对象的变量赋值为null。
(5)JS的垃圾回收机制?
没有用到的对象/变量。
无法访问的对象(几个对象形成一个环,互相引用)
(6)如何主动触发JS垃圾回收机制
执把这个值设置为 null
(7)垃圾回收方法【2种】
标记清除、引用计数
防抖和节流
(1)原理
防抖:输入完一段时间在调用【注册手机号/邮箱、表单点击】
节流:规定的时间内只执行一次【搜索列表、页面滚动、商品橱窗】
(2)创建表单demo
例:创建表单提交按钮,多次点击,希望只能点击一次,有什么解决方法?
(1)setTimeout 通常用这种方法(如果遇到多个的话,封装一个方法)
(2)【一刀切】一个按钮在请求 其他接口都不可再请(定义一个loading,初始 false. 请求之前改成true 请求完或是失败 false)
(3)vue自定义指令
proto 和 prototype 的区别
proto 是每个实例都有的属性;prototype 是构造函数才有的。
实例中的 proto 和构造函数的 prototype 指的是一个对象。
*number 有原型链。
注)proto 可以访问 prototype 上的属性。
(1) call、apply、bind 三者有什么区别?
【三者都可改变this 指向】
1、语法:
call(对象[,参数1,参数2,....]) //此地参数是指的是对象的参数,非方法的参数
apply(对象, 参数数组) // 参数数组形式: [参数1, 参数2, .....]
(2) 共同点:
(1)这三者都可以改变this指向。
(2)功能基本相同,都是实现继承/转换指针对象。与面象对象 extend 功能相似。
(3) 不同点:
(1)call 可以传多个参数,第二个参数和后面的参数可以是任意类型的数据。
(2)apply 最多支持两个参数,且第二个参数必须是数组。
(3)bind 参数和 call 相同,不会调用函数。bind 不会立马执行,会返回执行上下文被改变的函数。
(4)call 和 apply 的第一个参数都是 this 指向,直接执行。bind 是将某个函数绑定到对象上。
拷贝有哪几种方法?
(1)JSON.parse(JSON.stringify()) 【不能拷贝原型的方法,拷贝时间/正则会有问题】
(2)递归 :【达到 1000+,递归会爆】
(3)Object.assign: 【只适用于一层的对象】,可以使用Object.assign({},obj)
(4)JQ中的 extend
(5)lodash 中的 cloneDeep
(6)concat 方法:【连接两个或多个数组,不改变现有数组,得到连接数组的副本】
(7)ES6 的扩展运算符: 可以使用 […xxx]
注)vue的data 是浅拷贝,data 解决了独立性和复用性,调用一次就会有一个内存地址,和其他互不影响。
浅拷贝和深拷贝有什么区别?适用于什么场景?具体函数怎么写?
浅拷贝:复制引用数据类型的地址,如果改变内存中某个值,会影响另一个对象。【浅拷贝是拷贝的索引】
深拷贝:复制引用类型数据,如果改变其中一个值,不会影响另一个对象。【深拷贝的是数据】
注)es6 新增的方法都是深拷贝。当你使用某个对象的值,不想改变原对象,可以用深拷贝弄一个新的内存对象。
JSON.parse(JSON.stringify()) 深拷贝存在什么问题?
不能拷贝原型的方法,拷贝时间/正则也会有问题。
注)简单说就是能拷贝 array、string、number、boolean 这些扁平化的数据,JSON直接表示的。
递归进入死循环怎么解决?
通过边界值判断。
浏览器的回流与重绘
回流:页面结构发生变化时,会触发回流
页面一开始渲染
浏览器窗口大小变化
元素位置发生变化
元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代)
重绘:不改变页面结构,只改变元素的外观、风格;比如背景颜色等。
颜色修改
文本方向修改
阴影修改
注)回流一定会触发重绘,回流比重绘更消耗性能;
优化方法:修改元素时,尽量不修改原有样式,通过class 增加
浏览器的原理是什么?
(1)浏览器输入地址
(2)DNS解析
(3)TCP协议
(4)http发送请求
(5)服务器处理返回处理结果
(6)浏览器显示
浏览器为什么会跨域?【同源策略】
前后端分离的时候,前后端域名不一样,就会发生跨域问题。因为在请求中我们一般都是get/post请求。
注)同源策略:端口、协议、主机号 相同是就是同源。
跨域怎么解决?【6种】
(1)请求时头部加:header(‘Access-Control-Allow-Origin:*’)
(2)JSONP
(3)postMessage
(4)nginx 转发
(5)websocket
(6)本地 web 服务/cdn
JSON 和 JSONP 有什么区别?
JSON:返回的字符串。
JSONP:返回的脚本代码(包括函数)
(1)区别
JSON 是获取理想的数据,不能通过跨域请求;于是就把json 放在一个合法的语种中作为JS文件传过去。
json 是想要的东西;jsonp 是为了达到目的普遍采有的方法,最终也是json。
json 是目的;jsonp 是手段。json 总会用到,jsonp 跨域才会用到。
JSONP的原理?
通过 script 标签实现跨域请求。里面有src ,这个不受同源策略影响,哪都能访问。
es5的继承 和 es6的继承实现方法?以及不同点?
(1)首先要知道 JS 继承【4种】:原型继承、实继承、构造函数继承、拷贝继承。
(2)es5 主要是通过 property 或 构造函数 。
【es5 继承实质上是先创建子类的实例对象,然后将父级实例对象添加到 this 上】
(3)es6 通过 class 关键字,里面有构造函数,类之间通过 extends 关键字实现继承。
【与es5 继承机制完全不同。实质上是 先创建父类实例对象 this (必须先调用super 方法),然后用子类构造函数修改this】
基本类型和引用类型的区别?
(1)基本类型【number、boolean、string、null、undefined、symbol】
按值访问,可以对值进行操作。
(2)引用类型【object(array、function 等)】
注)简单来说通过 typeof 判断类型,如何值是 object/function 就是 引用类型。
typeof 返回的类型:string、boolean、undefined、object、function、number
typeof null // object
typeof object // function
typeof {} // object
typeof [] // object
JS 内置对象有哪些?
基本对象:object、boolean、symbol、function。
错误对象:error、
数字和日期对象:Date、bigInt、Number、Math。
字符串:正则(RegExp)、String
【详情请看链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects】
ajax 请求有几个方法?get 和 post 有什么区别?
请求方法【4个】:get、post、put、delete。也有 HEAD、TRACE、CONNECT、OPTIONS方法。
get:使用url 或者 cookie 传参,速度快,有大小限制,不安全;一般创建用。
post:装数据放在body中,无大小限制,速度慢,安全;一般删除/修改用。
直面理解:本质上一样,区别在于在浏览器限制不一样
注)百度搜索用的是 get 请求,为了方便用户。
JQ 中 click 和 on 的区别?
静态资源使用时没区别。动态时只有 on 能生效;
宏任务和微任务【另一种问法就是:事件轮洵】
(1)执行顺序:先同步后异步、先微任务再宏任务。
(2)任务队列中:外层任务-------微任务------宏任务
(3)宏任务:当时调用栈中执行的任务
【主代码块、setTimeout、setInterval、setImmediate、requestAnimationFrame】
(4)微任务:当前(此次事件循环中)宏任务执行完成,下一个宏任务开始之前需要执行的任务就是微任务
【promise.then、process.nextTick、MutationObserver、catch、finally】
注)宏任务的事件放在callback queue中,由事件触发线程维护;微任务的事件放在微任务队列中,由js引擎线程维护。
setTimeout、promise、async 有什么区别?
async 是基于 promise 实现的。promise 是 ES6。async 是 ES7。
1、setTimeout 是宏任务,回调函数放在宏任务队列中,等执行栈清空后执行。
2、promise 是微任务,回调函数放在宏任务中的微任务中,等宏任务中的同步代码执行完后在执行。
3、async 表示后面有异步操作,await 后面跟的是表达式。async 方法执行时,遇到 await 会立刻执行表达式,然后把表达式的代码放到i微任务中,让出执行栈让让同步代码先执行。
注)这个问题主要考查的是三者在循环中的区别,事件循环【2种】:宏任务、微任务。
promise 什么情况下会用到?有哪些方法?分别怎么使用?
(1)理解
没出现promise 的时候,都是嵌套使用。promise 出现简单来说是为了:回调解决异步的问题。
(2)promise 的方法
(1)promise.then()如果不出错,依次执行.then 方法。
(2)promise.all()可以并行执行多个操作,在一个回调中处理返回的数据。
(3)promise.catch().then 出错的时候走catch,有自己的 err 就会走自己的error 方法。
(4)promise.finally()不管状态如何,执行完then 和 catch 后都会执行 finally 方法。
(5)promise.race () 将多个promise 实例包装成一个。
(6)promise.resove() 将现有对象转成 promise 对象。
(7)promise.reject()
(3)应用场景
(1)图片加载的时候使用 promise 。
(2)all()实现多个请求合并在一起,汇总请求结果,只需要设置一个 loading。
(3)race ()设置图片请求超时。
注)用同步的方式写异步代码。promise 中只有三种状态:进行中、成功、失败。
使用promise 时,如果后面是 同步任务,直接 return,如果后面是异步任务,需要return 一个新的 promise 方法。
(4)promise.all ()出错的时候怎么解决?
1、如果有七八个接口处理【2种方法】:使用 promise.all 或者 async/await。
2、使用 promise.all()会出现其中一个任务失败就会走 catch,怎么解决?【2种方法】
(1)改为串行
(2)promise 捕获到 error 的时候,吃掉异常,改成 resolve,约定特殊格式表示调用成功
for循环分别采用var 和 let?
for 是同步任务;setTimeout 是 异步任务
(1)进行异步任务时for循环已经循环完毕,所以得到的值 i 是循环完成之后的同一个变量 i = 5;
(2)let具有块级作用域,每次循环时,块级作用域中的 i 都是独立存在的,而 js 引擎底层又对每次迭代的 i 的值进行了记忆,所以最后打印结果为 0 1 2 3 4;
for(var i = 0;i < 5; i++) {
setTimeout(() => {
console.log(i)
})
}
// var ->输出5次 5
// let -> 0,1,2,3,
js报错堆栈溢出?有啥办法定位到是哪段代码?
溢出就是死循环了,积累变量过多不释放,肯定会溢出的。
定位:只能排查代码看有没有递归方法;或者往里面存东西;或者元素移除了,绑定的事件没有移除掉。
前端如何渲染上万条数据?【主要问的点可能还是 setTimeout 】
1、document.createDocumentFragment( ) 用来创建一个虚拟节点,节点对象不属于文档树。
2、当需要添加多个 dom 的时候,可双先把 dom 添加到虚拟节点中,最后把虚拟节点添加到页面中,这样可以减少dom 操作次数。
3、JS 是单线程的,利用 setTimeout 和 setInterval 两个函数和多线程差不多
事件委托、事件冒泡、事件捕获
事件委托:自己不想做的事情让父级去做(利用事件冒泡原理,把事件加上父级身上)
事件冒泡:自上而下触发件 (阻止事件冒泡:event.stopPropagation())
事件捕获:通过document 获取事件节点,自上而下触发
注)DOM中有两个流:事件冒泡和事件捕获
缓存
(1)cookie(同源是 4KB;支持所有浏览器;一般登录的时候用到)
(2)localStorage(同源 5MB;不手动清除,会一直在浏览器中)
(3)sessionStorage(同源5MB;浏览器窗口关闭数据丢失)
用途【2】:高性能、高并发。
new操作符具体干了什么
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this
this 指向分别是什么?
(1)箭头函数中:指的上下文this,且不能改变。
(2)普通函数/定时器:指的 window。
(3)构造函数:指的是实例对象(new 会改变 this 指向)
(4)通过事件绑定的 this: 指的是绑定的这个 this
(5)对象中:该方法所性的对象
(6)在函数中:this 是隐式参数。
(7)函数外,浏览器中:this是全局对象。
(8)node.js 中:this 指的是 模块 import,导出 export。
(9)传递到evel 中:直接掉用this 指的 当前对象;间接掉用 this 指的是全局对象。
window 和 document 有什么区别?
1、window 是窗体;document 是页面。
2、document 是 window 的一个子对象。
3、document.localtion 可以改变当前页面的内容;window.location 可以用其他文档更换当前的。
4、window.location 是对象,document.localtion 不是对象。
JS 怎么捕获异常?
try{
// 运行代码
}catch {
// 错误运行
}
函数申明
函数声明 大于 变量申明
函数提升 规则 函数字面量式 == 变量申明
代码块内:函数申明会提升到作用域最上面,但是函数申明 大于 变量申明
ES6
es6 的新特性有哪些?
(1)let 和 const
(2)字符串模版【${abc}
】
(3)箭头函数
(4)promise
(5)解构
(6)import 和 export
(7)class 定义和继承
(8)对象扩展、函数扩展
(9)proxy
(10)map、set集合去重、for of
(11)扩展运算符【…obj】
箭头函数和普通函数有什么区别?
箭头函数是 es6 的新特性,是匿名函数,不可以使用new 创建,没有原型。
var、let 和 const 有什么区别?
var 的申明挂在 window 上,可以变量名提升。
let 是变量。
const 是常量,不能为空,一旦申明必需赋值;如果申明的是复合对象,可以修改其属性。
注)let 和 const 会形成块作用域,一旦申明不能重复
字符串扩展
1.检索字符串:
(1).includes(),语法格式为:A.includes(B,index),判断A是否包含B,index表示检索的位置,从第index位置开始检索
(2).startsWith(), 语法格式为:A.includes(B,index),判断A是否以B开头,index表示检索的位置,从第index位置开始检索
(3).endsWith(), 语法格式为:A.endsWith(B,index),判断A是否以B结尾,index表示对前index个字符进行检索,这三个语句的index都是可以省略的,省略则表示检索整个字符串。
2.重复字符串:
(1).语法格式为str.repeat(n);
(2).repeat()并不会改变原来的字符串,因此需要一个变量用来接收重复后的结果,
3.去除空白
(1).trim(),语法格式为str.trim(),用于去除字符串首尾的空格
(2).trimStart(),语法格式为str.trimStart(),用于去除字符串开始处的空格
(3).trimEnd(),语法格式为str.trimEnd(),用于去除字符串结尾处的空格
TS 和 es6 有什么区别?
TS的两种类型:内部、外部;作用域【3个】全局、局部、类作用域。
es6的两种类型:导入、导出;作用域【2个】全局、局部。
优化&使用
git 版本怎么控制?
撤回commit 操作:git reset --soft HEAD^
回退版本:git reset HEAD~
回退指定版本:git reset --hard id
修改 commit 描述:git reset --amend
git config --gobal --list
(1)流程
git stash (修改的文件放到缓存中)
git pull (拉取远程代码)
git stash pop (把放在缓存中的文件放出来)
git add . (添加要提交的文件)
git commit -m ‘’ (提交的文字描述)
git push orign 分支号 (提交到某个分支)
(2)常用
git fetch origin (拉取更新远程分支)
git config user.name ‘xxx’ (提交时显示的名字)
git config user.email ‘xxx’ (提交时显示的邮箱)
git clone xxx (克隆)
git checkout xxx (切换项目分支)
git checkout -b 分支 (创建一个分支并且停留在当前分支上)
git branch(拉取分支)
git branch -r (拉取远程分支)
git branch -r (当前本地有的分支)
git log (查看日志)
代码优化
(1)少用全局变量
(2)用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
(3)用setTimeout来避免页面失去响应
(4)缓存DOM节点查找的结果
(5)避免使用CSS Expression
(6)避免全局查询
(7)避免使用with(with会创建自己的作用域,会增加作用域链长度)
(8)多个变量声明合并
(9)避免图片和iFrame等的空Src。空Src会重新加载当前页面,影响速度和效率
(10)尽量避免写在HTML标签中写Style属性
常用的状态码有哪些?分别代表什么?
2xx:接口被正常处理
200:请求成功
204:请求成功,没有资源
3xx:浏览器需要特殊处理
301:永久定向,url资源位置换了,
302:临时定向,url临时换在别的位置
303:资源更新,必须用 GET 方法
304:资源找到,但是没有符合条件的
307:和 302 差不多
4xx:服务器无法请求
400:错误请求,服务器不能理解
401:需要 http 认证
403:禁止,服务器禁止请求
404:页面没找到
5xx:服务器处理出错
500:服务器内部错误
501:尚未实施
502:错误网关
503:服务器不能用(超载/停机)
504:网关超时
505:http版本不支持
JS 新特性
参考:【https://blog.csdn.net/weixin_46719229/article/details/122313154】
JS变量前面+是什么意思?【例:+value】
可以理解成 Number(value)
可选链操作符(?.)
允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效
参考链接: https://blog.csdn.net/weixin_38551805/article/details/116199959
const dog = {
prop: {
name: '小七',
ege: ''
}
}
dog?.prop?.name // '小七'
dog?.prop?.ege // ''
空值合并操作符(??)
只有当左侧为null和undefined时,才会返回右侧的数 空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
null ?? 20 // 20
undefined ?? 3 // 3
'' ?? 3 // ''
0 ?? 4 // 0
数组
JS有几种数据类【8种】
string、number、null、boolean、object、undefined、symbol、bigInt
注)symbol 是es6新增,这种类型对象永不相等,创建时传入相同的值,解决属性名冲突,做为标记。
bigint 是 chrome 67中出现的。
NaN 是什么?
非数值型,Number 中的一种特殊类型。
null 存在的原因是什么?怎么解决?
null 存在的原因是为空对象,不存在。
做判断的时候,把 null 判断放在最前面。
Js中 == 和 === 有什么区别?
== 值相等;=== 值和类型都相等。
判断是不是数组的方法【4】
(1)Array.isArray()
(2)constructor
(3)instanceof
(4)Object.prototype.toString.call(要判断的) === ‘[object Array]’
some 和 every 的返回值?
返回true 和 flase。some 一个满足就是,every 是所有满足。
常用于:购物车全选多选。
改变原数组
【push、pop、shift、unshift、splice、reverse、sort、forEach】
不改变原数组
【map、filter、reduce、join、slice、concat、some、every】
遍历数组
【map、filter、reduce、every、some、forEach】
typeof [“男裤”, “女裤”] 为啥是object 不是数组?
数组本身也是对象,判断是否是数组不能用typeof ,应该用Array.isArrray()或者 arr instanceof Arrray。这个问题问的点是基本数据类型。
map 和 forEach 有什么区别?
map不改变原数据,forEach 改变原数组。
注)创建新数组用map;不需要制作新数组,只需要改变的时候用forEach。
map、filter、forEach、some、every 能不能跳出当前循环?
map、filter、forEach 不能,用 break 也不行。
some 可以,return false
every 可以跳出当前循环: return activityrules
JS向上取整、向下取整、四舍五入、 m 的 n 次方?
【想问的点是JS内置对象】
(1)向上取整:Math.ceil(5.123) // 6
(2)向下取整:Math.floor(5.123) // 5
(3)四舍五入:Math.round(5.432) // 5
(4)绝对值:Math.abs(-1) // 1
(5)取最大值:Math.max(1,2,3) // 3
(6)取最小值:Math.min(1,2,3) // 1
(7)随机数:Math.random()
(8)m的n 次方:Math.pow(2,4) // 16 (2的4次方)
(9)保留小数:parseFloat(3.44) // 3.44
(10)保留整数:parseInt(3.44) // 3
数组常用的方法
(1)字符串转数组:split
(2)数组转字符串:join
(3)substring: 提取字符串,日期第几位到第几位
(4)indexOf: 第一次出现的索引位置
(5)lastIndexOf: 最后一次出现的索引位置
(6)push: 在数组后面添加一个值,返回新的长度。
(7)pop: 删除最后一个值,返回删除的值。
(8)shift: 删除第一个值,返回删除的值。
(9)unshift: 在数组前面添加一个值,返回新的长度。
(10)reverse: 从大到小排序。
(11)sort: 从小到大排序。
(22)splice :从数组中获取添加/删除对象,返回被删除的元素。
(23)slice :选回返定的元素。
(24)concat : 连接两个或多个数组,返回新的数组,不改变原数组
对象数组合并【concat、遍历+、apply】
(1)concat 方法【不改改原数组】
var a=[1,2,3],b=[4,5,6];
var c=a.concat(b);
console.log(c) // 1,2,3,4,5,6
(2)map遍历
var arr1=[1,2,3],arr2=[4,5,6];
arr2.map(item =>{
arr1.push(item)
})
console.log(arr1) // 1,2,3,4,5,6
(3)apply
var a=[1,2,3],b=[4,5,6];
a.push.apply(a,b)
console.log(a) // 1,2,3,4,5,6
对象合并【$extend、遍历+for in、Object.assign】
(1)$extend
var a= {'a': 1}, b= {'b': 1};
$.extend(a,b)
console.log(a)
(2)遍历 + for in
let a = {a: 1},b = {b: 2, c: 3, d: 4}
for(let i in b){
// hasOwnProperty是判断自有属性
// for in 循环遍历对象属性时,原型链上所有属性都将被访问,会避免原型对象扩展带来的干扰
if(b.hasOwnProperty(i) === true) {
a[i] = b[i]
}
}
console.log(a) // { a: 1, b: 2, c: 3, d: 4 }
(3)Object.assign
let a = {a: 1},b = {b: 2},c = {c: 3},d = {d: 4};
let arr = Object.assign(a,b,c,d)
console.log(a) // { a: 1, b: 2, c: 3, d: 4 }
console.log(arr) // { a: 1, b: 2, c: 3, d: 4 }
统计数组元素出现的次数
let demo = ['aa','bb','cc','aa','1','cc','cc','bb']
let obj = {}
demo.map(item =>{
if(obj[item]){
obj[item]=obj[item]+1;
} else {
obj[item] = 1
}
})
console.log(obj) // {1: 1, aa: 2, bb: 2, cc: 3}
for of 和 for in 有什么区别?for of 怎么能使对象遍历?
(1)区别:代码如下
for in 遍历获取的是对象的 key ,也就是索引值。
for of 遍历获取的是对象的 value,也就是对象的值。
var arr = ['red', 'green', 'blue']
(1)for in 遍历获取的是对象的 key,就是索引值
for(let item in arr) {
console.log('结果', item)
}
> "结果" "0"
> "结果" "1"
> "结果" "2"
(2)for of 遍历获取的是对象的 关键字,就是值
for(let item of arr) {
console.log('结果', item)
}
> "结果" "red"
> "结果" "green"
> "结果" "blue"
(3)for of 怎么能使对象遍历
数组去重有哪几种方法?【12种】数组去重和对象去重有什么区别?
(1)filter
(2)reduce + includes
(3)includes (判断是否有这个值)
(4)indexOf (通过 indexOf === -1 判断)
(5)for 嵌套,里面splice 去重
(6)sort 排序,和相邻元素对比
(7)[…new Set(arr)]
(8)set 去重
(9)递归去重
方法8:Array.from(new Set(arr))
方法3、4:includes、indexOf
function unique(arr) {
// 先判断是不是数组
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
// indexOf 方法
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i])
}
// includes 方法
if(!array.includes(arr[i])) {
array.push(arr[i])
}
}
return array;
}
方法5:for 嵌套,里面splice 去重
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
//数据
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//结果 [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重