# JavaScript
# 1 内置类型
JS
中分为七种内置类型,七种内置类型又分为两大类型:基本类型和引用类型(对象(Object
))。- 基本类型分六种:
null
,undefined
,boolean
,number
,string
,symbol
。 - 其中
JS
的数字类型是浮点类型的,没有整型。并且浮点类型基于 IEEE 754标准实现,在使用中会遇到某些Bug,NaN
也属于number
类型,并且NaN
不等于自身。 - 对于基本类型来说,如果使用字面量的方式,那个这个变量只是个字面量,只有在必要的时候才会转换为对应的类型。
let a = 111; // 这只是字面量,不是number类型
a.toString(); // 使用时候才会转换为对象类型
对象(Object)是引用类型,在使用过程中会遇到浅拷贝和深拷贝的问题。
let a = { name: 'fangyuan' };
let b = a;
b.name = 'yuanfang';
console.log(a.name); // yuanfang
# 2 null 和 undefined 的区别
null
是一个表示“无”的对象, 转为数值时为0; undefined
是一个表示“无”的原始值,转为数值时为 NaN
。
当声明的变量还未被初始化时,变量的默认值为 undefined
。 null
用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。
undefined
表示“缺少值”, 就是此处应该有一个值,但是还没有定义。典型用法是:
- (1) 变量被声明了,但没有赋值时,就等于undefined。
- (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
- (3) 对象没有赋值的属性,该属性的值为undefined。
- (4) 函数没有返回值时,默认返回undefined。
null
表示“没有对象”,即该处不应该有值。典型用法是:
- (1) 作为函数的参数,表示该函数的参数不是对象。
- (2) 作为对象原型链的终点。
# 3 new 操作符具体干来什么
- 1、创建一个空对象,并且
this
变量引用该对象,同时还继承来该函数的原型。 - 2、属性和方法被加入到
this
引用的对象中。 - 3、新创建的对象由
this
所引用,并且最后隐式返回this
。
# 4 call,apply,bind 三者的异同点
bind,apply,call三者都可以用来改变 this
的指向, 下面分别对他们进行比较分析
apply
和 call
二者都是 Function
对象的方法,每个函数都能调用二者的第一个参数都是你要指定的执行上下文, apply
和 call
的区别是: apply
接收的是一个包含多个参数的数组, 而 call
方法接收的是若干个参数列表。
bind
接收的是若干个参数列表,但必须要调用一次 bind(...)()
var a = {
name: 'fangyuan',
fn: function(a, b) {
console.log(a + b);
}
}
var b = a.fn;
b.apply(a, [1, 2]); // 3
b.call(a, 4, 5, 6); // 15
我们常常使用的验证是否是数组(前提是 toString()
方法没有被重写过):
function isArray(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
bind 与 apply、call的区别
var a = {
name: 'fangyuan';
fn: function(a, b) {
console.log(a + b);
}
}
var b = a.fn;
b.bind(a, 1, 2)(); // 3
我们发现 bind()
方法还需要调用一次;是由于 bind()
方法创建一个新的函数,我们必须手动去调用。
bind,aplly,call的共同点和不同点:
- 三者都可以用来改变
this
的指向 - 三者第一个参数都是
this
要指向的对象,也就是向指定的上下文,上下文就是指调用函数的那个对象。 - 三者都可以传参,但是
apply
是数组, 而call
和bind
是有顺序的传入多个参数。 - bind 是返回对应函数,便于稍后调用;
apply
、call
则是立即执行。
# 5跨域问题解决方式
- 通过 jsonp 跨域
- 跨域资源共享(CORS)
- nodejs 中间件代理跨域
- nginx 反向代理红设置 proxy_cookie_domain
1.通过jsonp跨域
通常为了减轻web服务器的负载,我们把js、css、img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script标签,再请求一个带参网址实现跨域通信。
<script>
var script = document.createElement('script);
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.xxx.com:8080/login?user=admin&callback=jsonCallback';
document.head.appendChild(script);
// 回调执行函数
function jsonCallback(res) {
alert(JSON.stringify(res));
}
</script>
2.跨域资源共享(CORS)
CORS是一个 W3C 标准,全称为“跨域资源共享”(Cross-origin resource sharing) 跨域资源共享CORS详解。看名字就知道这是处理跨域问题的标准做法。CORS有两种请求,简单请求和非简单请求。
简单请求
- 请求方法是
HEAD
、GET
、POST
三种方法之一 - HTTP 请求头的信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于 application/x-www-form-urlencoded、multipart/form-data、text/plain
复杂请求
如果前端要带 cookie
,前端也需要单独设置
var xhr = new XMLHttpRequest();
// 前端设置是否带cookie
xhr.withCredentials = true;
...
3.Nodejs中间件代理跨域
通过起一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite
参数修改响应头 cookie
中域名,实现当前域名下 cookie
的写入
在 vue
框架下实现跨域
利用 node + webpack + webpack-dev-server
代理接口跨域。 在开发环境下,由于 vue
渲染服务和接口代理服务都是 webpack-dev-server
同一个,所以页面与代理接口之间不再跨域,无须设置 headers
跨域信息了。后台可以不做任何处理。
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://xxx.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'xxx.com' // 可以为false, 表示不修改
}],
noInfo: true
}
}
4.Nginx反向代理中设置
和使用 node
中间件跨域原理相似。前端和后端都不需要写额外的代理来处理,只需要配置一下 Nginx
server {
listen: 80;
server_name aa.com;
location ^~ /api {
proxy_pass http://xxx.com;
}
}
# 6 script 标签中 defer 和 async 的区别
<script src="script.js"></script>
没有 defer
或 async
,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script
标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就立即加载并执行。
<script async scr="script.js"></script>
有 async
,加载和渲染后续文档元素的过程将和 script.js
的加载与执行并行执行(异步)。
<script defer src="script.js"></script>
有 defer
, 加载后续文档元素的过程将和 script.js
的加载并行进行(异步),但是 script.js
的执行要在所有元素解析完成之后, DOMContentLoaded
事件触发之前完成。
蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表HTML解析。
# 7 数据类型转换
JS中在使用运算符或者对比符时,会自带隐式转换,规则如下:
转换规则
-
、*
、/
、%
:一律转换成数值后计算+
:- 数字 + 字符串 = 字符串, 运算顺序是从左到右
- 数字 + 对象, 优先调用对象的
valueOf -> toString
- 数字 +
boolean/null
-> 数字 - 数字 +
undefined -> NaN
[1].toString() === '1'
{}.toString() === '[object object]'
NaN !== NaN
、+undefined
为NaN
在JS中类型转换只有三种情况,分别是:
- 转换为布尔值
- 转换为数字
- 转换为字符串
原始值 | 转换目标 | 结果 |
---|---|---|
number | 布尔值 | 除了 0、-0、NaN 都为 true |
string | 布尔值 | 除了空字符串都为 true |
undefined、null | 布尔值 | false |
引用类型 | 布尔值 | true |
number | 布尔值 | 5 => '5' |
Boolean、函数、Symbol | 字符串 | 'true' |
数组 | 字符串 | [1, 2] => '1, 2' |
对象 | 字符串 | '[Object Object]' |
string | 数字 | '1' => 1, 'a' => NaN |
数组 | 数字 | 空数组为0, 存在一个元素且数字转为数字,其他情况 NaN |
null | 数字 | 0 |
除了数组的引用类型 | 数字 | NaN |
Symbol | 数字 | 抛错 |
# 8 JavaScript - for...in、 for...of 和 forEach的不同点
for...in for...in
会迭代对象中所有的可枚举属性(迭代对象属性),当然也包括继承的可枚举属性。它可以对数组、字符串或者普通对象进行迭代,但不能对 Map
或 Set
对象进行迭代。
for (let prop in ['a', 'b', 'c'])
console.log(prop); // 输出数组的索引:0, 1, 2
for (let prop in 'str')
console.log(prop); // 输出字符串的索引: 0, 1, 2
for (let prop in {a: 1, b: 2, c: 2})
console.log(prop); // 输出对象的属性: a, b, c
for (let prop in new Set(['a', 'b', 'c']))
console.log(prop); // 没有可枚举的属性, 输出为空
for...of for...of
会对可以迭代的对象进行迭代,但它迭代的是对象的值,而不是对象的属性。它可以对数组、字符串、 Map
或 Set
对象进行迭代,但不能对普通对象进行迭代。
for (let val of ['a', 'b', 'c'])
console.log(val); // 输出数组的值:a, b, c
for (let val of 'str')
console.log(val); // 输出字符串中的字符:s, t, r
for (let val of {a: 1, b: 2, c: 3})
console.log(val); // 报错 (intermediate value)is not iterable
for (let val of new Set(['a', 'b', 'c']))
console.log(val); // 输出 Set 值: a, b, c
forEach() forEach()
是 Array
原型上的一个方法,它允许你迭代数组中的每一个元素。 forEach()
虽然只能迭代数组,但它在迭代的时候能访问每一个元素的值和索引。
['a', 'b', 'c'].forEach(val => console.log(val)); // 输出数组的值: a, b, c
['a', 'b', 'c'].forEach((val, i) => console.log(i)); // 输出数组的索引: 0, 1, 2
# 9 如何禁止网页复制粘贴
对于禁止网页复制粘贴,也许你并不陌生。 一些网页是直接禁止复制粘贴;一些网页,则是要求登录后才可复制粘贴;还有一些网站,复制粘贴时会带上网站的相关来源标识信息。
- 如何禁止网页复制粘贴
const html = document.querySelector('html);
html.oncopy = () => {
alert('你来复制呀');
return false;
};
html.onpaste = () => false;
- 在复制时做些别的操作,比如跳转登陆页面
const html = document.querySelector('html');
html.oncopy = (e) => {
console.log(e);
// 比如指向百度或者登陆页
window.location.href = 'http://www.baidu.com';
};
html.onpaste = (e) => {
console.log(e);
}
- 如何使用js设置/获取剪贴板内容
// 设置剪切板内容
document.addEventListener('copy', () => {
const clipboardData = event.clipboardData || event.originalEvent ?.clipboardData;
clipboardData?.setData('text/plain', '不管复制什么,都是我!');
event.preventDefault();
});
// 获取剪切板的内容
document.addEventListener('paste', () => {
const clipboardData = event.clipboardData || event.originalEvent?.clipboardData;
const text = clipboardData?.getData('text');
console.log(text);
event.preventDefault();
})
# 10 检测类型的常用方法及优缺点
不同类型的优缺点 | typeof | instanceof | constructor | Object.prototype.toString.call |
---|---|---|---|---|
优点 | 使用简单 | 能检测出引用类型 | 基本能检测所有的类型(null和undefined除外) | 能检测出所有的类型 |
缺点 | 只能检测出基本类型(null除外) | 不能检测出基本类型,且不能跨iframe | constructor容易被修改,也不能跨iframe | IE6下,undefined和null均为Object类型 |
# 11 ES6-ES12
# ES6
# 1. let 和 const
这两个的出现,总感觉是为了开发的代码规范而出现的。我们要逐渐放弃var,在项目中多用let 和 const
与var的区别:
- var 有变量提升(声明阶段),有初始化提升,值可变
- let 有变量提升(声明阶段),没有初始化提升,值可变
- const 有变量提升(声明阶段),没有初始化提升,值不可变,但如果是定义对象,则属性可变
暂时性死区
问题说明:其实 let
和 const
是有 变量提升
的,但是没有初始化提升:
var name = '方圆'
function fn () {
console.log(name)
let name = 'fangyuan'
}
fn() // Cannot access 'name' before initialization 无法在初始化之前调用
块级作用域解决问题
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
})
}
// 5 5 5 5 5
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
})
}
// 0 1 2 3 4
# 2. 默认参数
开发中你曾遇到这样的问题,如果参数不传进来,你就设置默认参数
function fn (name, age) {
var name = name || '方圆'
var age = age || 25
console.log(name, age)
}
fn() // 方圆 25
但是这么写确实不优雅,可以使用 ES6 的默认参数
function fn (name = '方圆', age = 25) {
console.log(name, age)
}
fn() // 方圆 25
fn('fangyuan', 22) // fangyuan 22
# 3. 扩展运算符
曾经的我,想要拼接多个数组,我只能这么做
const arr1 = [1,2,3]
const arr2 = [4,5,6]
const arr3 = [7,8,9]
const arr = arr1.concat(arr2).concat(arr3)
[
1,2,3,4,5,6,7,8,9
]
现在的我,可以更优雅地进行拼接
const arr1 = [1,2,3]
const arr2 = [4,5,6]
const arr3 = [7,8,9]
const arr = [...arr1, ...arr2, ...arr3]
[
1,2,3,4,5,6,7,8,9
]
# 4. 剩余参数
大家可能遇到过这种问题,一个函数,传入参数的个数是不确定的,这就可以用ES6的剩余参数
function fn (name, ...params) {
console.log(name)
console.log(params)
}
fn('fq', 1, 2) // fq [1, 2]
fn('fq', 1, 2, 3, 4, 5) // fq [1, 2, 3, 4, 5]
# 5. 模板字符串
以前的我,拼接字符串只能这么做
const name = 'fq'
const age = '22'
console.log(name + '今年' + age + '岁啦') // fq今年22岁啦
现在我可以这么做,会更优雅
const name = 'fq'
const age = 22
console.log(`${name}今年${age}岁啦`) // fq今年22岁啦
# 6. Object.keys
可以用来获取对象的key的集合,进而可以获得对应key的value
const obj = {
name: 'fq',
age: 22,
gender: '男'
}
const keys = Object.keys(obj)
console.log(keys) // ['name', 'age', 'gender']
# 7. 箭头函数
以前我们使用普通函数
function fn () {}
const fn = function () {}
ES6新加了 箭头函数
const fn = () => {}
// 如果只有一个参数,可以省略括号
const fn = name => {}
// 如果函数体只有一句return
const fn = name => {return 2 * name}
// 可简写为
const fn = name => 2 * name
// 如果返回的是对象
const fn = name => ({ name })
普通函数和箭头函数的区别:
- 1、箭头函数不可作为构造函数,不能使用new
- 2、箭头函数没有自己的this
- 3、箭头函数没有arguments对象
- 4、箭头函数没有原型对象
# 8. Array.prototype.forEach
ES6 新加的数组遍历方法
const eachArr = [1, 2, 3, 4, 5]
// 三个参数:遍历项 索引 数组本身
// 配合箭头函数
eachArr.forEach((item, index, arr) => {
console.log(item, index, arr)
})
// 1 0 [ 1, 2, 3, 4, 5 ]
// 2 1 [ 1, 2, 3, 4, 5 ]
// 3 2 [ 1, 2, 3, 4, 5 ]
// 4 3 [ 1, 2, 3, 4, 5 ]
// 5 4 [ 1, 2, 3, 4, 5 ]
# 9. Array.prototype.map
常用语返回一个处理过后的新数组
const mapArr = [1, 2, 3, 4, 5]
// 三个参数:遍历项 索引 数组本身
// 配合箭头函数,对每一个元素进行翻倍
const mapArr2 = mapArr.map((num, index, arr) => 2 * num)
console.log(mapArr2) // [2, 4, 6, 8, 10]
# 10. Array.prototype.filter
顾名思义,用来过滤的方法
const filterArr = [1, 2, 3, 4, 5]
// 三个参数:遍历项 索引 数组本身
// 配合箭头函数,返回大于3的集合
const filterArr2 = filterArr.filter((num, index, arr) => num > 3)
console.log(filterArr2) // [4, 5]
# 11. Array.prototype.some
some,意思就是只有一个是真,那就返回真
const someArr = [false, true, false, true, false]
// 三个参数:遍历项 索引 数组本身
// 配合箭头函数,只要有一个为true,就返回true,一个true都没有,就返回false
const someArr2 = someArr.soem((bol, index, arr) => bol)
console.log(someArr2) // true
# 12. Array.prototype.every
every跟some是相反的,some是只要有一个就行,every是要所有为真才返回真
const everyArr = [false, true, false, true, false]
// 三个参数:遍历项 索引 数组本身
// 配合箭头函数,需要所有为true,才返回true,否则返回false
const everyArr2 = everyArr.every((bol, index, arr) => bol)
console.log(everyArr2) // false
# 13. Array.prototype.reduce
- 第一个参数为
callback函数
:pre
为上次
return的值,next
为数组的本次
遍历的项 - 第二个参数为
初始值
,也是第一个pre
举例:
// 计算 1 + 2 + 3 + 4 + 5
const reduceArr = [1, 2, 3, 4, 5]
const sum = reduceArr.reduce((pre, next) => {
return pre + next
}, 0)
console.log(sum) // 15
// 统计元素出现个数
const nameArr = ['fq', 'fq', 'fq1', 'fq2', 'fq1', 'fq']
const totalObj = nameArr.reduce((pre, next) => {
if (pre[next]) {
pre[next]++
}else {
pre[next] = 1
}
return pre
}, {})
console.log(totalObj) // { 'fq': 3, 'fq1': 2, 'fq2': 1}
# 14. 对象属性同名简写
以前同名属性需要这么写
const name = 'fq'
const age = 22
const obj = {
name: name,
age: age
}
console.log(obj) // { name: 'fq', age: 22}
ES6新增语法,只需这么写
const name = 'fq'
const age = 22
// 属性同名可简写
const obj = {
name,
age
}
console.log(obj) // { name: 'fq', age: 22}
# 15. Promise
Promise
,中文名为承诺,承诺在哪呢?承诺在,一旦他的状态改变,就不会再改。这里就介绍基本使用,如果想要深入理解如何使用,请看我的另一篇文章 《ES6标准入门》(阮一峰)--16.Promise 对象 (opens new window)
看看基本使用
- 成功状态
function requestData () {
// 模拟请求
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fq')
}, 1000)
})
}
requestData().then(res => {
console.log(res) // 一秒后输出 'fq'
}, err => {
console.log(err)
})
- 失败状态
function requestData () {
// 模拟请求
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('错误啦')
}, 1000)
})
}
requestData().then(res => {
console.log(res)
}, err => {
console.log(err) // 一秒后输出 '错误啦'
})
all
方法- 接收一个 Promise 数组,数组中如有非Promise项,则此项当做成功
- 如果所有 Promise 都成功,则返回成功结果数组
- 如果有一个 Promise 失败,则返回这个失败结果
// 如果全都为成功
function fn (time) {
return new Promise((resolve, reject) => {
console.log(88)
setTimeout(() => {
resolve(`${time}毫秒后我成功啦!!!`)
}, time)
})
}
Promise.all([fn(2000), fn(3000), fn(1000)]).then(res => {
// 3 秒后输出 ['2000毫秒后我成功啦!!!', '3000毫秒后我成功啦!!!', '1000毫秒后我成功啦!!!']
console.log(res)
}, err => {
console.log(err)
})
// 如果有一个失败
function fn(time, isResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
isResolve ? resolve(`${time}毫秒后我成功啦!!!`) : reject(`${time}毫秒后我失败啦!!!`)
}, time)
})
}
Promise.all([fn(2000, true), fn(3000), fn(1000, true)]).then(res => {
console.log(res)
}, err => {
console.log(err) // 3秒后输出 '3000毫秒后我失败啦'
})
race
方法- 接收一个 Promise数组,数组中如有非Promise项,则此项当做成功
- 哪个Promise
最快
得到结果,就返回那个结果,无论成功失败
function fn (time, isResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
isResolve ? resolve(`${time}毫秒后我成功啦!!!`) : reject(`${time}毫秒后我失败啦!!!`)
}, time)
})
}
Promise.race([fn(2000, true), fn(3000), fn(1000)]).then(res => {
console.log(res)
}, err => {
console.log(err) // 1秒后输出
})
# 16. class
以前咱们使用构造函数生成对象,这么做
function Person (name) {
this.name = name
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const kobe = new person('科比')
kobe.sayName() // 科比
而有了 ES6 的 class
可以这么做
class Person {
constructor (name) {
// 构造器
this.name = name
}
sayName () {
console.log(this.name)
}
}
const kobe = new Person('科比')
kobe.sayName() // 科比
值得一提的是, class
本质也是 function
, class
是 function
的 语法糖
class Person {}
console.log(typeof Person) // function
除了以上,还需要知道class的以下知识点
静态属性和静态方法,使用 static
定义的属性和方法只能class自己用,实例用不了
class Person {
constructor(name) {
this.name = name
}
static age = 22
static fn () {
console.log('哈哈')
}
}
console.log(Person.age) // 22
Person.fn() // 哈哈
const my = new Person('方圆')
console.log(my.age) // undefined
my.fn() // fn is not a function
extend
继承
class Animal {
constructor (name, age) {
this.name = name
this.age = age
}
}
class Cat extends Animal {
say () {
console.log(this.name, this.age)
}
}
const cat = new Cat('fq', 22) // 继承了 Animal 的构造器
cat.say() // fq 22
# 17. 解构赋值
以前提取对象里的属性需要这么做
const obj = {
name: 'fq',
age: 22,
gender: '男'
}
const name = obj.name
const age = obj.age
const gender = obj.gender
console.log(name, age, gender) // fq 22 男
ES6 新增了解构赋值的语法
const obj = {
name: 'fq',
age: 22,
gender: '男'
doing: {
morning: '摸鱼',
afternoon: '摸鱼',
evening: 'sleep'
}
}
const { name, age, gender } = obj
console.log(name, age, gender) // fq 22 男
// 解构重名
const { name: myname } = obj
console.log(myname) // fq
// 嵌套解构
const { doing: { evening }} = obj
console.log(evening) // sleep
也可以进行数组的解构
const arr = [1, 2, 3]
const [a, b, c] = arr
console.log(a, b, c) // 1 2 3
// 默认赋值
const [a, b, c, d = 5] = arr
console.log(a, b, c, d) // 1 2 3 5
// 乱序解构
const { 1: a, 0: b, 2: c} = arr
console.log(a, b, c) // 2 1 3
# 18. find 和 findIndex
- find:找到返回被找元素,找不到返回 undefined
- findIndex:找到返回被找元素索引,找不到返回 -1
const findArr = [
{ name: '科比', no: '24' },
{ name: '罗斯', no: '1' },
{ name: '利拉德', no: '0' }
]
const kobe = findArr.find(({name}) => name === '科比')
const kobeIndex = findArr.findIndex(({name}) => name === '科比')
console.log(kobe) // { name: '科比', no: '24'}
console.log(kobeIndex) // 0
# 19. for in 和 for of
- for in:遍历方法,可遍历对象和数组
- for of:遍历方法,只能遍历数组,不能遍历非iterable对象
先看for in:
const obj = { name: 'fq', age: 22, gender: '男'}
const arr = [1, 2, 3, 4, 5]
for (let key in obj) {
console.log(key)
}
// name
// age
// gender
for (let index in arr) {
console.log(index)
}
// 0 1 2 3 4
再看 for of:
for (let item of arr) {
console.log(item)
}
// 1 2 3 4 5
# 20. Set 和 Map
Set
先说说
Set
的基本用法// 可不传数组 const set1 = new Set() set1.add(1) set1.add(2) console.log(set1) // Set(2) {1, 2} // 也可传数组 const set2 = new Set([1, 2, 3]) // 增加元素 使用 add set2.add(4) set2.add('fq') console.log(set2) // Set(5) { 1, 2, 3, 4, 'fq'} // 是否含有某个元素 使用 has console.log(set2.has(2)) // true // 查看长度 使用 size console.log(set2.size) // 5 // 删除元素 使用 delete set2.delete(2) console.log(set2) // Set(4) { 1, 3, 4, 'fq'}
再说说
Set
的不重复性// 增加一个已有元素,则增加无效,会被自动去重 const set1 = new Set([1]) set1.add(1) console.log(set1) // Set(1) { 1 } // 传入的数组中有重复项,会自动去重 const set2 = new Set([1, 2, 'fq', 3, 3, 'fq']) console.log(set2) // Set(4) { 1, 2, 'fq', 3}
Set
的不重复性,要注意引用数据类型和NaN
// 两个对象都是不同的指针,所以没法去重 const set1 = new Set([1, { name: 'fq'}, 2, { name: 'fq'}]) console.log(set1) // Set(4) { 1, { name: 'fq'}, 2, { name: 'fq'}} // 如果是两个对象是同一指针,则能去重 const obj = { name: 'fq'} const set2 = new Set([1, obj, 2, obj]) console.log(set2) // Set(3) { 1, { name: 'fq'}, 2} 咱们都知道 NaN !== NaN, NaN 是自身不等于自身的,但是在 Set 中他还是会被去重 const set = new Set([1, NaN, 1, NaN]) console.log(set) // Set(2) { 1, NaN}
利用 Set 的不重复性,可以实现数组去重
const arr = [1, 2, 3, 4, 4, 5, 5, 66, 88, 99] // Set 可利用扩展运算符转为数组 const quchongArr = [...new Set(arr)] console.log(quchongArr) // [1, 2, 3, 4, 5, 66, 88, 99]
Map
Map
对比object
最大的好处就是,key
不受限制// 定义 map const map1 = new Map() // 新增键值对 使用 Set(key, value) map1.set(true, 1) map1.set(1, 2) map1.set('哈哈', '嘻嘻嘻') console.log(map1) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻'} // 判断 map 是否含有某个 key 使用has(key) console.log(map1.has('哈哈')) // true // 获取map中某个key 对应的value使用get(key) console.log(map.get(true)) // 1 // 删除 map 中某个键值对 使用delete(key) map1.delete('哈哈') console.log(map1) // Map(2) { true => 1, 1 => 2} // 定义map, 也可以传入键值对数组集合 const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']]) console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }
# ES7
# 21. includes
传入元素,如果数组中能找到此元素,则返回true,否则返回false
const includeArr = [1, 2, 3, 'fq', 'fq1']
const isKobe = includeArr.includes('fq')
console.log(isKobe) // true
跟 indexOf 很像,但还是有区别的
const arr = [1, 2, NaN]
console.log(arr.indexOf(NaN)) // -1 indexOf 找不到 NaN
console.log(arr.includes(NaN)) // true includes 能找到NaN
# 22. 求幂运算符
以前求幂,我们需要这么写
const num = Math.pow(3, 2) // 9
ES7 提供了求幂运算符:**
const num = 3 ** 2 // 9
# ES8
# 23. Object.values
可以用来获取对象的value集合
const obj = {
name: 'fq',
age: 22,
gender: '男'
}
const values = Object.values(obj)
console.log(values) // ['fq', 22, '男']
# 24. Object.entries
可以用来获取对象的键值对集合
const obj = {
name: 'fq',
age: 22,
gender: '男'
}
const entries = Object.entries(obj)
console.log(entries)
[['name', 'fq'],['age',22],['gender','男']]
# 25. async/await
这个是很常用的语法了,我是理解就是:以同步方式执行异步操作
我们平时可能hi遇到这种场景,接口一,请求到数据一,而数据一被当做请求二的参数去请求数据二,我们会用Promise这么做
function fn () {
// 模拟第一次请求
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5)
}, 1000)
}).then(res => {
// 模拟第二次请求
new Promise((resolve, reject) => {
setTimeout(() => {
// 拿第一次请求的数据去乘10,当做第二次请求的数据
resolve(res * 10)
}, 2000)
}).then(sres => {
console.log(sres)
})
})
}
fn() // 3秒后输出 50
这样的嵌套是不美观的,如果有很多个接口,那就会嵌套很多层,此时我们可以使用async/await来以同步方式执行异步,注意以下几点:
- await 只能在async函数里使用
- await 后面最好接Promise,如果后面接的是普通函数则会直接执行
- async 函数返回的是一个Promise
function fn1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5)
}, 1000)
})
}
function fn2 (data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(data * 10)
}, 2000)
})
}
async function req () {
// 同步方法执行异步,像排队一样
const data1 = await fn1() // 等待1秒后返回数据再往下执行
const data2 = await fn2(data1) // 拿data1去请求2秒后,往下走
console.log(data2) // 总共3秒后 输出 59
}
req()
# ES9
# 26. for await of
可用于循环执行多个 await/async
我们来看以下场景哈
function fn (time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time}毫秒后我成功啦!!!`)
}, time)
})
}
fn(3000).then(res => console.log(res))
fn(1000).then(res => console.log(res))
fn(2000).then(res => console.log(res))
结果是
1000毫秒后我成功啦!!!
2000毫秒后我成功啦!!!
3000毫秒后我成功啦!!!
但是想要这个结果
3000毫秒后我成功啦!!!
1000毫秒后我成功啦!!!
2000毫秒后我成功啦!!!
第一时间我们肯定想到的是 async/await
function fn (time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time}毫秒后我成功啦!!!`)
}, time)
})
}
async function asyncFn () {
// 排队
const data1 = await fn(3000)
console.log(data1) // 3秒后 3000好秒后我成功啦!!!
const data2 = await fn(1000)
console.log(data2) // 再过1秒 1000毫秒后我成功啦!!!
const data3 = await fn(2000)
console.log(data3) // 再过2秒 2000毫秒后我成功啦!!!
}
但是上面代码也是有缺点的,如果有几十个,那不是得写几十个await,有没有一种方法可以通过循环来输出呢?
function fn (time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time}毫秒后我成功啦!!!`)
}, time)
})
}
async function asyncFn () {
const arr = [fn(3000), fn(1000), fn(1000), fn(2000), fn(500)]
for await (let x of arr) {
console.log(x)
}
}
asyncFn()
3000毫秒后我成功啦!!!
1000毫秒后我成功啦!!!
1000毫秒后我成功啦!!!
2000毫秒后我成功啦!!!
500毫秒后我成功啦!!!
# 27. Promise.finally
新增的 Promise 方法,无论失败或者成功状态,都会执行这个函数
// cheng
new Promise((resolve, reject) => {
resolve('成功咯')
}).then(res => {
console.log(res)
}, err => {
console.log(err)
}).finally(() => { console.log('我是finally')})
new Promise((resolve, reject) => {
reject('失败l')
}).then(
res => { console.log(res) },
err => { console.log(err)}
).finally(() => { console.log('我是finally') })
# ES10
# 28. Array.flat
- 可用于降维
有一个二维数组,想让他变成一堆数组:
const arr = [1, 2, 3, [4, 5, 6]]
console.log(arr.flat()) // [1, 2, 3, 4, 5, 6]
还可以传 参数
,参数为降维的次数
const arr = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]
console.log(arr.flat(2))
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
如果传的是一个无限大的数字,那么就实现了多维数组(无论几维)降为一堆数组
const arr = [1, 2, 3, [4, 5, 6], [7, 8, 9, [10, 11, 12]]]
console.log(arr.flat(Infinity))
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
# 29. Array.flatMap
现在给你一个需求
let arr = ["科比 詹姆斯 安东尼", "利拉德 罗斯 麦科勒姆"];
将上面数组转为
[ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]
第一时间想到 map + flat
console.log(arr.map(x => x.split(' ')).flat())
// [ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]
flatMap
就是 flat + map
, 一个方法顶两个
console.log(arr.flatMap(x => x.split(' ')))
[ '科比', '詹姆斯', '安东尼', '利拉德', '罗斯', '麦科勒姆' ]
# 30. BigInt
BigInt
是ES10新加的一种JavaScript数据类型,用来表示大于 2^53 - 1
的整数,2^53 - 1
是ES10之前,JavaScript所能够表示最大的数字
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n
const hugeString = BigInt("9007199254740991");
// 9007199254740991n
const hugeHex = BigInt("0x1fffffffffffff");
// 9007199254740991n
const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111");
// 9007199254740991n
哦对了,既然是 JavaScript新的数据类型,那他的 typeof
是啥?
const bigNum = BigInt(1728371927189372189739217)
console.log(typeof bigNum) // bigint
所以以后面试官问你JavaScript有多少种数据类型,别傻傻答6种了,要答8种,把ES6的Symbol和ES10的BigInt也加上去
数据类型: `null`, `undefined`, `string`, `number`, `boolean`, `object`, `symbol`, `bigint`
# 31. Object.fromEntries
前面ES8的 Object.entries
是把 对象转成键值对数组
,而 Object.fromEntries
则相反,是把 键值对数组转为对象
const arr = [
['name', 'fq'],
['age', 22],
['gender', '男']
]
console.log(Object.fromEntries(arr)) // { name: 'fq', age: 22, gender: '男'}
它还有一个用处,就是把 Map转为对象
const map = new Map()
map.set('name', 'fq')
map.set('age', 22)
map.set('gender', '男')
console.log(map) // Map(3) { 'name' => 'fq', 'age' => 22, 'gender' => '男'}
const obj = Object.fromEntries(map)
console.log(obj) // { name: 'fq', age: 22, gender: '男'}
# 32. String.trimStart && String.trimEnd
咱们都知道 JavaScript
有个 trim
方法, 可以清楚字符串首尾的空格
const str = ' fq '
console.log(str.trim()) // 'fq'
timeStart
和 timeEnd
用来单独去除字符串的 首
和 尾
的空格
const str = ' fq '
// 去除首部空格
console.log(str.trimStart()) // 'fq'
// 去除尾部空格
console.log(str.trimEnd()) // ' fq'
# ES11
# 33. Promise.allSettled
ES11 新增的 Promise的方法
- 接收一个 Promise 数组,数组中如有非 Promise项, 则此项目当做成功
- 把每一个 Promise 的结果,集合成功数组,返回
function fn (time, isResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
isResolve ? resolve(`${time}毫秒后我成功啦!!!`) : reject(`${time}毫秒后我失败啦!!!`)
}, time)
})
}
Promise.allSettled([fn(2000, true), fn(3000), fn(1000)]).then(res => {
console.log(res)
})
// 3秒后输出
[
{ status: 'fulfilled', value: '2000毫秒后我成功啦!!!' },
{ status: 'rejected', reason: '3000毫秒后我失败啦!!!' },
{ status: 'rejected', reason: '1000毫秒后我失败啦!!!' }
]
# 34. ?. 和 ??
- 先说说
?.
,中文名为可选链
比如我们需要一个变量,是数组且有长度,才做某些操作
const list = null
// do something
if (list && list.length) {
// do something
}
// 使用可选链
const list = null
// do something
if (list?.length) {
// do something
}
比如有一个对象,我要取一个可能不存在的值,甚至我们都不确定obj是否存在
const obj = {
cat: {
name: '哈哈'
}
}
const dog = obj && obj.dog && obj.dog.name // undefined
// 可选链
const obj = {
cat: {
name: '哈哈'
}
}
const dog = obj?.dog?.name // undefined
比如有一个数组,我不确定它存不存在,存在的话就取索引为1的值
const arr = null
// do something
const item = arr && arr[1]
// 可选链
const arr = null
// do something
const item = arr?.[1]
比如有一个函数,我们不确定它存不存在,存在的话就执行它
const fn = null
// do something
const res = fn && fn()
// 可选链
const fn = null
// do something
const res = fn?.()
- 再说说
??
,中文名为空位合并运算符
请看以下代码,咱们使用 ||
运算符,只要左边是 假值
,就会返回右边的数据
const a = 0 || '林三心' // 林三心
const b = '' || '林三心' // 林三心
const c = false || '林三心' // 林三心
const d = undefined || '林三心' // 林三心
const e = null || '林三心' // 林三心
而??
和||
最大的区别是,在??
这,只有undefined
和null
才算假值
const a = 0 ?? '林三心' // 0
const b = '' ?? '林三心' // ''
const c = false ?? '林三心' // false
const d = undefined ?? '林三心' // 林三心
const e = null ?? '林三心' // 林三心
# ES12
# 35. Promise.any
意思有点像 || 一对即对,全错才错
E12新增的Promise的方法
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 如果有一个Promise成功,则返回这个成功结果
- 如果所有Promise都失败,则报错
// 当有成功的时候,返回最快那个成功
function fn(time, isResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
isResolve ? resolve(`${time}毫秒后我成功啦!!!`) : reject(`${time}毫秒后我失败啦!!!`)
}, time)
})
}
Promise.any([fn(2000, true), fn(3000), fn(1000, true)]).then(res => {
console.log(res) // 1秒后 输出 1000毫秒后我成功啦
}, err => {
console.log(err)
})
// 当全都失败时
function fn(time, isResolve) {
return new Promise((resolve, reject) => {
setTimeout(() => {
isResolve ? resolve(`${time}毫秒后我成功啦!!!`) : reject(`${time}毫秒后我失败啦!!!`)
}, time)
})
}
Promise.any([fn(2000), fn(3000), fn(1000)]).then(res => {
console.log(res)
}, err => {
console.log(err) // 3秒后 报错 all Error
})
# 36. 数字分隔符
数字分隔符可以让你在定义长数字时,更加地一目了然
const num = 1000000000
// 使用数字分隔符
const num = 1_000_000_000
# 37. ||= 和 &&=
或等于(||=) a ||= b => a || (a = b);
且等于(&&=) a &&= b => a && (a = b);
# 38. 对象计算属性
我们经常碰到这样的问题,无论是在微信小程序还是React中,我们需要根据某个条件去修改某个数据
if (type === 'boy') {
this.setData({
boyName: name
})
} else if (type === 'gril') {
this.setData({
grilName: name
})
}
我也不知道这个新特性叫啥,我就自己取名叫 属性动态属性
hahaha
this.setData({
[`${type}Name`]: name
})
# 小白必学JS优雅简洁写法
当我们刚开始学习JS代码时,我们只需要掌握JS中对应知识点就好,随着对JS代码熟悉程度,我们就要思考如何写出更优雅、更简洁的代码。
接下来我分享10种常用JS代码功能,通过常规写法和优雅写法的对比,来体现其优雅和简洁性。代码中用了ES6新特性,如果你对ES6不了解,可以先收藏好。在后期的VUE中,基本都在和ES6打交道
# 1、数组合并
常规写法
利用concat方法来合并数组
const apples = ['红苹果', '烂苹果'];
const fruits = ['西瓜', '草莓', '葡萄'].concat(apples);
console.log(fruits); // ['西瓜','草莓','葡萄', '红苹果','烂苹果']
优雅写法
利用ES6中的 ...扩展运算符
来合并数组
const apples = ['红苹果', '烂苹果'];
const fruits = ['西瓜', '草莓', '葡萄', ...apples];
console.log(fruits); // ['西瓜', '草莓', '葡萄', '红苹果', '烂苹果']
# 2、数组中取值
常规写值
利用数组下标一个一个从数组中取数据
const num = [1, 2];
const num1 = num[0];
const num2 = num[1];
console.log(num1, num2); // 1 2
优雅写法
利用ES6的解构赋值来取值
const num = [1, 2];
const [num1, num2] = num;
console.log(num1, num2);
# 3、对象取值
常规写法
对象.属性名 的方式获取属性值
const user = {
name: 'zhangsan',
age: 30
};
const name = user.name;
const age = user.age;
console.log(name, age); // "zhangsan" 30
优雅写法
利用ES6的解构赋值来实现
const user = {
name: "zhangsan",
age: 30
}
const { name, age } = user;
console.log(name, age); // "zhangsan" 30
# 4、数组循环
常规写法
利用for 循环来遍历数组,从而取值
const fruits = ['西瓜','草莓','葡萄','苹果'];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i])
}
优雅写法
利用ES6的for...of来遍历数组取值
const fruits = ['西瓜','草莓','葡萄','苹果'];
for (fruit of fruits) {
console.log(fruit);
}
# 5、回调函数
常规写法
forEach 中回调函数为普通函数
const fruits = ['西瓜','草莓','葡萄','苹果'];
fruits.forEach(function (fruit) {
console.log(fruit); // 西瓜 草莓 葡萄 苹果
})
优雅写法
forEach 中回调函数为箭头函数,如果箭头函数中只有一句代码,则可以省略{}
const fruits = ['西瓜','草莓','葡萄','苹果'];
fruits.forEach((fruit) => console.log(fruit)); // 西瓜 草莓 葡萄 苹果
# 6、数组搜索
常规写法
数组中保存着每一条水果的信息,我们通过输入水果名,到数组中查找对应的信息。
利用常规的for循环遍历来查找
const fruits = [
{ name: '苹果', order: 1},
{ name: '梨子', order: 4},
{ name: '香蕉', order: 2},
];
function getApples (arr, value) {
for (let index = 0; index < arr.length; index ++) {
if (arr[index].name === value) {
return arr[index];
}
}
}
const result = getApples(fruits, '苹果');
console.log(result); // { name: '苹果', order: 1}
优雅写法
利用数组的find方法来实现搜索
const fruits = [
{ name: '苹果', order: 1},
{ name: '梨子', order: 4},
{ name: '香蕉', order: 2},
];
function getApples(arr, value) {
return arr.find((obj) => obj.name === value);
}
const result = getApples(fruits, '梨子');
console.log(result); // { name: '梨子', order: 4}
# 7、字符串转换为数字
常规写法
利用parseInt来实现
const num = parseInt("10");
console.log(num, typeof num); // 10 "number"
优雅写法
利用+号来实现,不过只是针对纯数字的字符串有效
const num = + "10";
console.log(num typeof num); // => 10 "number"
console.log(+"10" === 10); // true
# 8、null值初始化
常规写法
通过if判断,如果为null,则初始化值为“普通用户”
// 获取用户角色
function getUserRole (role) {
let userRole;
if (role) userRole = role;
else userRole = "普通用户";
return userRole;
}
console.log(getUserRole()); // 普通用户
console.log(getUserRole("管理员")); // 管理员
优雅写法
通过 || 或短路运算符来实现
function getUserRole (role) {
return role || '普通用户'; // 默认值定义的常见方法
}
console.log(getUserRole()); // '普通用户'
console.log(getUserRole('管理员')); // '管理员'
# 9、字符串拼接
常规写法
const name = '张三';
const age = 23;
const message = '大家好,我叫' + name + '今年' + age + '岁了!';
console.log(message); // 大家好,我叫张三,今年23岁了!
优雅写法
const name = '张三';
const age = 23;
const message = `大家好,我叫${name},今年${age}岁了!`;
console.log(message); // Hi DevPoint!
# 10、对象合并
常规写法
利用for循环来遍历
const employee = { name: '张三', age: 30};
const salary = { grade: 'A'};
const summary = salary; // 用来做合并后对象
for (const key in employee) {
summary[key] = employee[key];
}
console.log(summary); // { grade: 'A', name: '张三', age: 30 }
优雅写法
利用es6的扩展运算符和解构赋值来实现
const employee = { name: '张三', age: 30};
const salary = { grade: 'A'};
const summary = { ...employee, ...salary };
console.log(summary); // { grade: 'A', name: '张三', age: 30 }