JS基础,ES6

176 阅读13分钟

基础数据类型

在ES5的时候,我们认知的数据类型确实是 6种:Number、String、Boolean、undefined、object、Null。

  • JS的数据类型有8种:Number、String、Boolean、Null、undefined、object、symbol、bigInt
  • 基本类型(单类型):除Object。 String、Number、boolean、null、undefined。
  • 引用类型:object。里面包含的 function、Array、Date。

数字(Number)

  1. 包括浮点数和整数;
    • 什么是浮点数:浮点数就是该数值中必须有一个小数点,并且小数点后面必须至少有一位数字。
  2. 特殊的数字类型NaN
    • (Not a Number)表示不是数字,但是其实它是一个特殊的数字;NaN具有传染性,即NaN参与任何运算,结果都为NaN;NaN与任何数值都不相等
  3. isNaN()函数
    • 判断一个数是否是NaN,如果是NaN则返回true,否则返回false
  4. Infinity
    • 数据超过了JS可以表示的范围,是一个特殊的数字
    • 数字除以0得到Infinity
  5. 经典面试题
  • 0.1+0.2为什么不等于0.3? 0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现 了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。

字符串(String)

  • 多个字符的有序序列,双引号和单引号引起来的都是字符串 字符串特点:

1、单引号和双引号引起来的都是字符串

2、两个字符串相加,实际上是将两个字符串拼接

3、两个字符串相加,是拼接而不是运算,其他运算结果为NaN,表示计算错误

4、字符串加数字,首先将数字转化为响应的字符串然后再参与运算。同样不支持-, *, /等其他运算

布尔值(Boolean)

包括true和false,通常用在流程控制语句,选择判断语句;

常见false值:

  1. 数字0
  2. NaN
  3. “ ”,空字符串
  4. false
  5. undefined
  6. null true值: 除了false值就是true值

Undefined

  • 如果使用一个未定义的变量,会得到一个undefined值,当定义了一个变量未初始化,默认的也会给它初始化成undefined值

null

  • 代表什么也没有;注意:null和undefined的最大区别是如果变量值为null,说明变量是存在的,只不过它的值是空值null

Object

  • 其中包含了Data、function、Array等。这三种是常规用的
  • 数组对象时间函数等等都是object

symbol

  • 这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,做为标记。

bigInt

  • 指安全存储、操作大整数。(但是很多人不把这个做为一个类型)

如何区分数据类型

1.typeof

typeof是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括number,string,boolean,undefined,object,function,symbol等。

2.instanceof  

instanceof用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。instanceof检测的是原型,内部机制是通过判断对象的原型链中是否有类型的原型。

3.constructor

当一个函数F被定义时,JS引擎会为F添加prototype原型,然后在prototype上添加一个constructor属性,并让其指向F的引用,F利用原型对象的constructor属性引用了自身,当F作为构造函数创建对象时,原型上的constructor属性被遗传到了新创建的对象上,从原型链角度讲,构造函数F就是新对象的类型。这样做的意义是,让对象诞生以后,就具有可追溯的数据类型。

4.Object.prototype.toString()

toString()是Object的原型方法,调用该方法,默认返回当前对象的[[Class]]。这是一个内部属性,其格式为[object Xxx],其中Xxx就是对象的类型。对于Object对象,直接调用toString()就能返回[object Object],而对于其他对象,则需要通过call、apply来调用才能返回正确的类型信息。即:Object.prototype.toString.call(null) //[Object Null]

  封装一个准确判断数据类型的函数

  let type  = typeof obj;
  if(type != "object"){
    return type;
  }
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}

当对象进行类型转换时

显示类型转换

Number() / parseFloat() / parseInt()/String() / toString()/Boolean()

  • 1、数组转字符串 join()
  • 2、字符串转数组split()

隐示类型转换

+ - == !><= <= >=

题目
true + 0
{}+[]
4 + {} 
4 + [1] 
'a' + + 'b'
console.log ( [] == 0 )
console.log ( ! [] == 0 )
console.log ( [] == ! [] )
console.log ( [] == [] )
console.log({} == !{})
console.log({} == {})

一做不知道,一错就知道

1.  1
1.  0
1.  "4[object Object]"
1.  "41"
1.  "aNaN"
1.  true
1.  true
1.  true
1.  false
1.  false
1.  false

JS数据的循环遍历

1. for循环

使用临时变量,将长度缓存起来,避免重复获取数组长度,当数组较大时优化效果才会比较明显。

for(j = 0,len=arr.length; j < len; j++) {`
}

2. foreach循环

  • 遍历数组中的每一项,没有返回值,对原数组没有影响,不支持IE;
  • 数组自带的foreach循环,使用频率较高,实际上性能比普通for循环弱
  • 注意:不能使用breakcontinue跳出整个循环或当前循环的,会报错,但是结合try...catch可以实现跳出循环
arr.forEach((item,index,array)=>{
    //执行代码`
})
//参数:value数组中的当前项, index当前项的索引, array原始数组;
//数组中有几项,那么传递进去的匿名回调函数就需要执行几次;

// 使用try...catch...可以跳出循环  后面同理
try {
   let arr = [1, 2, 3, 4];
   arr.forEach((item) => {
       // 跳出条件
       if (item === 3) {
           throw new Error("LoopTerminates");
       }
       console.log(item);
   });
} catch (e) {
    if (e.message !== "LoopTerminates") throw e;
};

3. for in循环

  • 用于遍历数组或者对象的属性;这个循环很多人爱用,但实际上,经分析测试,在众多的循环遍历方式中,它的效率是最低的;
  • 会把某个类型的原型(prototype)中方法与属性给遍历出来,所以这可能会导致代码中出现意外的错误
for(j in arr) {   
}

4. for of循环 (ES6)

  • 可以正确响应break、continue和return语句;
  • 这种方式是es6里面用到的,性能要好于forin,但仍然比不上普通for循环
for(let value of arr) {  
};

5. map循环(ES6)

  • 有返回值,可以return出来 map的回调函数中支持return返回值;return的是啥,相当于把数组中的这一项变为啥(并不影响原来的数组,只是相当于把原数组克隆一份,把克隆的这一份的数组中的对应项改变了);
  • 注意:不能使用breakcontinue跳出整个循环或当前循环的,会报错,但是结合try...catch可以实现跳出循环
arr.map(function(n){   
});

6. filter循环(ES6)

  • 不会改变原始数组,过滤出符合条件的元素并返回一个新数组
var arr = [
	{ id: 1, name: '买笔', done: true },
	{ id: 2, name: '买笔记本', done: true },
	{ id: 3, name: '练字', done: false }
]  
var newArr = arr.filter(function (item, index) {
	return item.done
})
console.log(newArr)
// [{ id: 1, name: '买笔', done: true },{ id: 2, name: '买笔记本', done: true }]

7. some循环(ES6)

  • 遍历数组,只要有一个以上的元素满足条件就返回 true,否则返回 false
var bool = arr.some(function (item, index) { return item.done }) console.log(bool) // true

8. every循环(ES6)

  • 遍历数组,每一个元素都满足条件 则返回 true,否则返回 false
var bool = arr.every(function (item, index) { return item.done }) console.log(bool) // false

9. find循环(ES6)

  • 遍历数组,返回符合条件的第一个元素,如果没有符合条件的元素则返回 undefined
var arr = [1, 1, 2, 2, 3, 3, 4, 5, 6]
var num = arr.find(function (item, index) {
	return item === 3
})
console.log(num)   //  3

10. findIndex循环(ES6)

  • 遍历数组,返回符合条件的第一个元素的索引,如果没有符合条件的元素则返回 -1
var arr = [1, 1, 2, 2, 3, 3, 4, 5, 6]
var num = arr.findIndex(function (item) {
	return item === 3
})
console.log(num)   //  4

11. reduce循环(ES6)

  • 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值。
  • 接受一个函数,函数有四个参数,分别是:上一次的值,当前值,当前值的索引,数组
  • reduceRight循环反之
var total = [0,1,2,3,4].reduce((a, b)=>a + b); //10
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){
    return previousValue + currentValue;
});

12. keys,values,entries(ES6)

-entries(),keys()和values() —— 用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

面试问forEach,map,filter的区别,怎么跳出循环?

  • 不能使用breakcontinue跳出整个循环或当前循环的,会报错,但是结合try...catch可以实现跳出循环
  • forEach和map都是遍历一个数组,但它们的返回值不同。forEach的返回值为undefined,不可以链式调用,而map回调函数的返回值会组成一个新数组,新数组的索引结构和原数组一致,原数组不变。
  • filter会返回原数组的一个子集,回调函数用于逻辑判断,返回 true则将当前元素添加到返回数组中,否则排除当前元素,原数组不变。
  • map和filter方法不会改变原数组。
  • 这三个方法都不会对空数组进行检测或执行回调函数。
  • 没有办法终止或者跳出forEach()循环,除非抛出异常,所以想执行一个数组是否满足什么条件,返回布尔值,可以用一般的for循环实现,或者用Array.every()或者Array.some()

ES6

上面列举了很多ES6的遍历方法,哪还知道其它ES6的吗?

  • let && const

  • iterable类型

  • 解构赋值

  • => 箭头函数

  • ... 展开运算符

  • ES7新特性

    • Array.prototype.includes
    • Exponentiation Operator(求幂运算)
  • ES8新特性

    • Object.values/Object.entries
    • String padding(字符串填充)
    • Object.getOwnPropertyDescriptors
    • 函数参数列表和调用中的尾逗号(Trailing commas)
    • 异步函数(Async Functions)

箭头函数this的指向

  1. 普通函数的this:指向它的调用者,如果没有调用者则默认指向window.
  2. 箭头函数的this: 指向箭头函数定义时所处的对象,而不是箭头函数使用时所在的对象,默认使用父级的this.

如何改变this指针

1.call() 方法
  • 第一个参数表示要把this指向的新目标,第二个之后的参数其实相当于传参,参数以,隔开    (性能较apply略好)
2.apply() 方法
  • apply() 与call()非常相似,不同之处在于提供参数的方式,apply()使用参数数组,而不是参数列表
3.bind()方法
  • bind()创建的是一个新的函数(称为绑定函数),与被调用函数有相同的函数体,当目标函数被调用时this的值绑定到 bind()的第一个参数上
//call 的传参和apply的传参
function say(arg1,arg2){
  console.log(this.name,arg1,arg2);
};
var obj = {
  name : 'tom',
  say : function(){
    console.log(this.name);
  }
}
say.call(obj,'one','two');//tom one two
say.spply(obj,['one','two']);//tom one two  效果一样

var foo = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            /* Act on the event */
            console.log(this.bar);      //1
        }.bind(this));//这里的this是eventBind的this,即指向的是foo
    }
}

宏任务和微任务

看题

console.log('start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

new Promise((resolve) => {
  console.log('promise')
  resolve()
})
  .then(() => {
    console.log('then1')
  })
  .then(() => {
    console.log('then2')
  })

console.log('end')

结果

start 
promise
end
then1
then2
setTimeout

所以为什么呢

  • ES6 规范中,microtask 称为 jobs,macrotask 称为 task
  • 宏任务是由宿主发起的,而微任务由JavaScript自身发起。
宏任务(macrotask)微任务(microtask)
谁发起的宿主(Node、浏览器)JS引擎
具体事件1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js)1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js)
谁先运行后运行先运行
会触发新一轮Tick吗不会

可参考 www.jianshu.com/p/bfc3e319a…

防抖和节流

参考链接:www.jianshu.com/p/f9f6b637f…

  • 会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频繁触发事件,咋办呢?这时候就应该用到函数防抖和函数节流了

函数防抖(debounce)

 函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

简单的说,当一个动作连续触发,则只执行最后一次。

打个比方,坐公交,司机需要等最后一个人进入才能关门。每次进入一个人,司机就会多等待几秒再关门。

函数防抖的应用场景

连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

函数防抖的实现原理

函数防抖的简单实现:

const _.debounce = (func, wait) => {
  let timer;

  return () => {
    clearTimeout(timer);
    timer = setTimeout(func, wait);
  };
};

函数防抖在执行目标方法时,会等待一段时间。当又执行相同方法时,若前一个定时任务未执行完,则 clear 掉定时任务,重新定时。

函数节流(throttle)

    限制一个函数在一定时间内只能执行一次。

举个例子,乘坐地铁,过闸机时,每个人进入后3秒后门关闭,等待下一个人进入

函数节流的应用场景

间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 谷歌搜索框,搜索联想功能
  • 高频点击提交,表单重复提交

函数节流的实现原理

1)函数节流的 setTimeout 版简单实现

const _.throttle = (func, wait) => {
  let timer;

  return () => {
    if (timer) {
      return;
    }

    timer = setTimeout(() => {
      func();
      timer = null;
    }, wait);
  };
};

函数节流的目的,是为了限制函数一段时间内只能执行一次。因此,通过使用定时任务,延时方法执行。在延时的时间内,方法若被触发,则直接退出方法。从而,实现函数一段时间内只执行一次。

2)函数节流的时间戳版简单实现
根据函数节流的原理,我们也可以不依赖 setTimeout实现函数节流。

const throttle = (func, wait) => {
  let last = 0;
  return () => {
    const current_time = +new Date();
    if (current_time - last > wait) {
      func.apply(this, arguments);
      last = +new Date();
    }
  };
};

其实现原理,通过比对上一次执行时间与本次执行时间的时间差与间隔时间的大小关系,来判断是否执行函数。若时间差大于间隔时间,则立刻执行一次函数。并更新上一次执行时间。

异同比较

相同点:

  • 都可以通过使用 setTimeout 实现。
  • 目的都是,降低回调执行频率。节省计算资源。

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
  • 函数防抖关注一定时间连续触发,只在最后执行一次,而函数节流侧重于一段时间内只执行一次。