# 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

当声明的变量还未被初始化时,变量的默认值为 undefinednull 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。

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 的指向, 下面分别对他们进行比较分析

applycall 二者都是 Function 对象的方法,每个函数都能调用二者的第一个参数都是你要指定的执行上下文, applycall 的区别是: 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 是数组, 而 callbind 是有顺序的传入多个参数。
  • bind 是返回对应函数,便于稍后调用;applycall 则是立即执行。

# 5跨域问题解决方式

  1. 通过 jsonp 跨域
  2. 跨域资源共享(CORS)
  3. nodejs 中间件代理跨域
  4. 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有两种请求,简单请求和非简单请求。

简单请求

  • 请求方法是 HEADGETPOST 三种方法之一
  • 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>

没有 deferasync ,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就立即加载并执行。

  <script async scr="script.js"></script>

async ,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行执行(异步)。

  <script defer src="script.js"></script>

defer, 加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后, DOMContentLoaded 事件触发之前完成。

script-defer-async

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表HTML解析。

# 7 数据类型转换

JS中在使用运算符或者对比符时,会自带隐式转换,规则如下:

转换规则

  • -*/%:一律转换成数值后计算
  • +:
    • 数字 + 字符串 = 字符串, 运算顺序是从左到右
    • 数字 + 对象, 优先调用对象的 valueOf -> toString
    • 数字 + boolean/null -> 数字
    • 数字 + undefined -> NaN
  • [1].toString() === '1'
  • {}.toString() === '[object object]'
  • NaN !== NaN+undefinedNaN

在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 会迭代对象中所有的可枚举属性(迭代对象属性),当然也包括继承的可枚举属性。它可以对数组、字符串或者普通对象进行迭代,但不能对 MapSet 对象进行迭代。

  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会对可以迭代的对象进行迭代,但它迭代的是对象的值,而不是对象的属性。它可以对数组、字符串、 MapSet 对象进行迭代,但不能对普通对象进行迭代。

  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 有变量提升(声明阶段),没有初始化提升,值不可变,但如果是定义对象,则属性可变

暂时性死区 问题说明:其实 letconst 是有 变量提升 的,但是没有初始化提升:

  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, classfunction语法糖

  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'

timeStarttimeEnd 用来单独去除字符串的 的空格

  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 || '林三心' // 林三心

??||最大的区别是,在??这,只有undefinednull才算假值

  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 }