对象转基本类型
//Symbol.toPrimitive的优先级最高
let a = {
valueOf(){
return 0;
},
toString(){
return '1';
},
[Symbol.toPrimitive](){
return 2;
}
}
1+a // =>3
'1' + a //=>'12'
复制代码
四则运算符
1 + '1' //'11'
2 * '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' //->"aNaN"
//+ 'b' --->NaN
//+ '1' ->1
复制代码
== 操作符
[] == ![] // -> true 解析如下
//[] 转成true ,然后取反变成 false
[] == false
则 [] == ToNumber(false)
则 [] == 0
则 ToPrimitive([]) == 0
//[].toString() ->''
则 '' == 0
则 0 == 0 ---> true
复制代码
闭包
定义:函数A返回一个函数B,并且函数B中使用了函数A的变量,则函数B就被称为闭包
function A(){
let a = 1
function B(){
console.log(a)
}
return B
}
复制代码
经典面试题:循环中使用闭包解决var定义函数的问题
for( var i=1;i<=5; i++ ){
setTimeout( function timer() {
console.log( i );
}, i*1000);
}
由于setTimeout 是个异步函数,所以会先把循环全部执行完毕,
这时 i 是6了
;所以会输出一堆的6
复制代码
解决方法:
* 1.使用闭包
for (var i = 1; i <=5 ;i++ ){
( function(j){
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i)
}
* 2.使用setTimeout的第三个参数
for (var i = 1; i <= 5; i++){
setTimeout(function timer(j){
console.log(j)
}, i*1000, i);
}
* 3.使用 let定义 i
for (let i = 1; i <= 5; i++ ){
setTimeout( function timer(){
console.log( i );
},i*1000);
}
复制代码
深浅拷贝
let a = {
age : 1
}
let b = a
a.age = 2
console.log(b.age) //2
即 若给一个变量赋值一个对象,那么两者的值会是同一个
引用,其中一方改变,另一方也会相应的改变。
复制代码
解决以上问题方式如下:
浅拷贝: Object.assign 或 展开运算符 (浅拷贝只能解决第一层问题)
let a = {
age : 1
}
let b = Object.assign({}, a) 或 let b = {...a}
a.age = 2
console.log( b.age) //1
复制代码
深拷贝 JSON.parse(JSON.stringify(object)
let a = {
age : 1,
jobs: {
first:'FE'
}
}
let b = JSON.parse(JSON.stringify(a)
a.jobs.first = 'native'
console.log(b.jobs.first) //FE
<!--局限性 会忽略undefined symbol ;
不能序列化函数
不能解决循环引用的对象-->
复制代码
![](https://user-gold-cdn.xitu.io/2019/1/1
模板化 在右Babel的情况下,可用直接使用ES6的模板化 commonJS是node独有的规范
防抖
在滚动事件中需要做一个复杂计算或者 实现一个按钮的方二次点击操作。
(频繁的事件回调中做复杂计算,很可能造成页面卡顿,所以不如将多次计算合并为一次计算,只在一个精确点做操作)
ps:防抖和节流 的作用是防止函数多次调用;区别:假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况 会每隔一定时间(参数wait)调用函数。
<!--
func 是用户传入需要防抖的函数
wait是等待时间
-->
const debounce = (func, wait = 50) =>{
//缓存 一个定时器id
let timer = 0
//
return function(...args) {
if(timer) clearTimeout(timer)
timer = setTimeout(() =>{
func.apply(this, args)
},wait)
}
}
复制代码
- 搜索时,用户输入完最后一个字才调用查询接口, 延迟执行的 防抖函数
- 点赞 如star是,立马反馈是否star成功,使用立即执行的防抖函数
function debounce (func ,wait = 50 ,immediate = true){
let timer, context,args;
const later = () =>setTimeOut(() => {
timer = null
if(!immediate){
func.apply(context,args)
context = args = null
}
},wait)
return function(...params) {
if(!timer){
timer = later()
if(immediate){
func.apply(this,params)
}else{
context = this
args = params
}
}else{
clearTimeout(timer)
timer = later()
}
}
}
复制代码
总结:
- 对于按钮放点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。 一旦你点累,定时器时间到,定时器重置为null,就可以再次点击了。
- 对于延迟执行函数来说的实现:清除定时器id,如果是延迟调用就调用函数
节流
节流和防抖动的本质是不一样的。防抖动是 多次执行变成最后一次执行,节流是将多次执行变成每隔一段时间执行。
继承
class MyDate extend Date{
test(){
rreturn this.getTime()
}
}
let myDate = new MyDate()
myDate.test()
复制代码
需要用Babel来编译代码 底层限制:若不是Date构造出来的实例,就不能调用Date的函数
function MyData(){
}
MyData.prototype.test = function(){
retrun this.getTime()
}
let d = new Date()
Object.setPrototypeOf(d,MyData.prototype)
Object.setPrototypeOf(MyData.prototype,Date.prototype)
即先创建父类实例 =》 改变实例原先的_proto_转而连接到子类的prototype=》
子类的prototype的 _proto_改为父类的prototype
通过这种方式实现的继承可以完美解决 JS底层限制
复制代码
call ,apply ,bind区别
call和apply: 都是为了解决改变this的指向,只是传参的方式不同。 除了第一个参数外,call可以接收一个参数列表, apply只接受一个参数数组。 bind:作用也是解决改变this的指向,只是该方法返回一个函数。
let a = {
value:1
}
function getValue(name ,age){
console.log(name)
console.log(age)
console.log(this.value)
}
getValue.call(a,'yck','24')
getValue.apply(a,['yck','24'])
复制代码
Promise 实现
Promise是Es6 新增的语法,解决了回调地狱的问题 看成一个状态机: 初始pending,通过函数resolve 和reject 将状态改成了 resolved和 rejected 状态,并且状态一旦改变就不能再次变化
then函数会返回一个Promise实例,并且该返回值是一个新的实例而不是之前的实例。因为Promise规范规定处理pending状态,其他状态是不可以改变的, 所以then本质上可以看成是 flatMap
Generator实现
Generator是ES6中新增的语法,都可以异步编程。
function* test(){
let a = 1+2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next()
复制代码