JS和ES6基础与常用技巧

AI摘要

这篇文章深入介绍了 JavaScript 和 ES 基础知识以及常用技巧。文章首先讨论了变量基础,包括不同的声明方式和常用数据类型。接着详细解释了变量声明、作用域、相等判断和空数据判断等内容。随后,文章介绍了对象和数组的操作方法,并讨论了常见的字符串拼接和简单类型转换技巧。此外,文章还涉及了网页定时任务、Promise、函数与箭头函数、模块化等主题,为读者提供了丰富的知识内容和实用技巧。

变量基础

JS变量一般可以用varconstlet声明,老版本可以直接赋值,不用声明。

常用类型:未定义(undefined)、空(null)、数字(Number)、字符串(String)、布尔值(Boolean)、符号(Symbol)、任意大整数(BigInt)、对象(Object),对象中有各种比较常用的数据,如函数、数组等

变量声明

  1. ES5var变量和函数会提升

    1. 函数变量提升到作用域最前面

    2. console.log(a); // 不会报错,但是值为:undefined
      b(); // 正常调用
      var a = 1;
      function b(){
      console.log(1);
      }
    3. 声明变量一定不要漏掉var

    4. c = 1; // 不允许不声明直接用,会变成全局变量,var必须
  2. 只要是支持ES6语法的,用const,非必要不用letESLint可以帮助优化

    1. const a = 1; // 看到是const变量就知道后面不会被更改,是对象或者数组需要注意,如果对象和数组不能改:Object.freeze()
      let b = 1;
      b = 2;
    2. 函数声明,下面几种都可以,不过有细微区别

    3. function a(){} // 函数声明,会提升到作用域最前面,可以提前调用
      var b = function(){} // 声明变量,然后赋值一个匿名函数,b会提升,变量可以使用,但是是undefined,不能提前调用
      const c = () => {} // ES6箭头函数,不会提升,不能提前使用变量和调用
      const d = function(){} // 和前一个有细微区别,主要是函数和箭头函数的区别,后面会提到 

注意:新项目应该都淘汰var了,不要再使用var,容易引起各种问题。

变量作用域

  1. ES6之前变量作用域,只有函数才有自己的作用域

    1. for(var i=0;i<10;i++){
        // 执行逻辑
      }
      console.log(i) // 输出10
      
      (function(){
        var k = 1;
      })() // 立即执行函数包裹之后不会有变量逃出作用域
      console.log(k) // 报错
  2. ES6中代码块就有自己的作用域

    1. for(let j=0;j<10;j++){
        // 执行逻辑
      }
      console.log(j) // 报错,j不存在

ES6解决了很多以前必须用闭包才能解决的JS问题。

数据基础

JS中经常需要做数据比较,比较是否相等,判断数据是否为空,如果为空不能继续支持某些操作,否则可能报错

相等判断

a === b // 判断类型
a == b // 不判断类型,1 == '1'
NaN !== NaN // 需要用isNaN()方法判断

注意:新项目一定不要用==,开启ESlint可以帮忙验证

空数据

JS中常见的空数据:

undefined
null
0 // 字符串'0'不为空
false
NaN
''

注意JS{}[]不是空,这种对象字面量是相当于用new构造出来,也不相等:{}!=={}

判空代码

const a = ''
if(!a){ // 不要用a===''之类的判断
    console.log('a为空:a=', a) 
}
if(a){
    console.log('有值:a=', a) 
}

如果是简单代码一行代码,可以用&&方法

const a = '';
a && console.log('执行操作'); // 不会执行后面的代码: console.log('执行操作')

最新链式操作,使用问号点?.操作符可以减少大量的判断:

const a = null // 假设a是数组,类似这种结构:[{name: 'gary'}]
// const a = [{name: 'gary'}]
let b
if(a && a[0]){
    b = a[0].name // 常规获取name属性
}
let c = a && a[0] && a[0].name // 骚操作获取name
let d = a?.[0]?.name // 最新链式操作

||和&&

这两个经常在判断语句中出现,其实这个表达式并不会返回Boolean值,返回的是实际的数据

const a = 0 || 1
console.log(a) // 1
const b = 'abc' && 'def'
console.log(b) // 'def'

字符串拼接

ES6之前字符串拼接一般都是用加号,而且不支持多行

const a = 'a'
const b = a + '\n' + 'b'

ES6有新的模板字符串语法,支持变量,而且可以换行,特别比较方便使用,尤其是拼接动态HTML

const a = 'a'
const b = `<span class="test">
${a}
</span>`

简单类型转换

const a = '1'
const b = +a // 转成数字, parseInt和parseFloat也可以,不过没有+简洁方便
const c = !!a // 转成Boolean

const d = 1
const e = ''+d // 转字符串
const f = `${d}` // 新模板字符串

正则表达式

参考:https://www.runoob.com/jsref/jsref-obj-regexp.html

正则表达式是独立于JS语言的

const re1 = new RegExp("\\w+"); // new方式新建
const re2 = /\w+/; // 字面量形式新建
const cardNo = '5122322139920319'
const cardConfusion = cardNo.replace(/^(\d{6}).*(\d{4})/, '$1******$2') // 512232******0319 分组替换

默认值

经常在开发中用到如果没有数据就给一个默认数据的情况,常规用法是用if语句

let a = ''
if(!a){
    a = '默认值'
}

代码相对来讲比较繁琐,有一些比较方便的方法。

函数默认值

如果默认值是传递给函数,ES6函数支持默认值,默认值参数可以在中间:

function a(config={}){
}
function b(b1, b2=2, b3){
}
b(1) // b1=1, b2=2
b(1,3) // b1=1,b2=3 函数传参不能跳跃
b(1,3,4) // b1=1,b2=3,b3=4

代码中默认值

除了函数默认值之外,平时代码内部也可能用到默认值

config = config || '默认值' // 最常用默认值使用方法
config = config ?? '默认值' // 比较新的语法,老的环境可能不支持,和上面区别在于只要有值(非undefined和null)就不会用默认值

函数与箭头函数

箭头函数和函数基本可以相互替代,但是还是有不少区别

  1. 箭头函数没有this,且不能绑定this

    1. vuemethods声明方法的时候如果要用this访问vue实例数据就不能用箭头函数。

    2. 因为没有this,以前的applycall改变this指向的方法都没有用处。

    3. const a = {
        b(){
            console.log(this) 
        },
        c:()=>{
            console.log(this) 
        }
      }
      a.b() // a对象
      a.c() // 全局window对象
      const a1 = {}
      a.b.call(a1) // a1对象
      a.c.call(a1) // 全局window对象
  2. 箭头函数没有arguments

    1. 普通函数可以用arguments聚合参数,arguments是伪数组,已经不推荐使用了

    2. ES6环境下可以用剩余参数代替,剩余参数支持普通函数和箭头函数

    3. function(a, b, ...args) { // 剩余参数是数组,只能在最后一个参数位置
      console.log(args)
      }
  3. 箭头函数没有原型

    1. 普通函数实际上是可以作为构造函数生成对象,可以作为类使用,不是很纯粹的函数

    2. 箭头函数是纯粹的函数,没有原型prototype,不能给prototype增加信息

    3. function A(){}
      console.log(A.prototype) // {}
      console.log(new A()) // A{}
      
      const B = () => {}
      console.log(B.prototype) // undefined
      console.log(new B()) // 报错 Uncaught TypeError: B is not a constructor

推荐使用箭头函数,尤其是在作为回调函数的时候,如promise.thenarray.filterarray.map,和vue结合的时候箭头函数不会引起this指向错误

对象和数组

对象和数组是JS中使用最频繁的数据,就是基本数据类型的组合形式

简单遍历

遍历数组

const arr = [1,2,3,4]
for(let i=0;i<arr.length;i++){
    console.log(arr[i])
}
for(const item of arr){
    console.log(item)
}
arr.forEach(item => console.log(item))

遍历对象

const obj = {a:1,b:2,c:3}
for(const key in obj){
    console.log(`${key}: ${obj[key]}`)
}
for(const key of Object.keys(obj)){
    console.log(`${key}: ${obj[key]}`)
}
for(const [key,value] of Object.entries(obj)){
    console.log(`${key}: ${value}`)
}

对象合并

如果是简单的对象合并,合并后对象时原始对象的浅层拷贝,方法比较多

展开运算

ES6中支持展开运算,很方便构造新的对象

   const a = {a:1,b:2}
   const b = {b:3}
   const c = {...a, ...b, a: 3} // 后面的覆盖前面的属性,产生新对象
Object方法

Object.assign可以实现相同的功能,不过代码比较繁琐,适合直接修改对象内容

   const a = {a:1,b:2}
   const b = {b:3}
   const c = Object.assign({}, a, b, {a:3}) // 后面的覆盖前面的属性

对象操作

对象操作方法很多,这里使用一些常用方法

const a = {} // 新建对象,一般用这种字面量方式,不用new Object(),语法简洁
const b = 'name'
const c = {
    a: a,
    b,
    [b]: '测试名字'
}
// 常用方法
const d = Object.assign({}, c)
Object.freeze(d) // 冻结对象,任何数据修改不能生效
Object.hasOwn(c, 'name') // 是否存在属性判断,代替c.hasOwnProperty('name')
Object.fromEntries([['a', '1'], ['b', '2']]) // 数组转对象
Object.entries(d) // 对象转数组

//====================================================
const arr = [{id:1, name: 'n1'}, {id:2, name: 'n2'}]
const obj1 = Object.fromEntries(arr.map(item=>[item.id, item])) // 常见的数据数组转成对象格式
const obj2 = arr.reduce((res, item) => {
    res[item.id] = item
    return res
}, {}) // 常见的数据数组转成对象格式
// ===================================================
Object.defineProperty(obj2, 'name', {}) // 定义变量描述符,重要,vue2响应式原理

数组操作

JS中除了对象就是数组,数组操作很多,尤其是ES6之后,增加了大量的方法

参考:https://www.runoob.com/jsref/jsref-obj-array.html

const arr = [1,2,5,3,4,1,3]
const arr1 = arr.concat([3,4,4]) // 数组合并,不改变源arr
const arr11 = [...arr, 3,4,4] // 数组合并,不改变源arr
const h = Math.max(...arr) // 展开传参
arr.push(1) // 后面增加
arr.pop() // 后面弹出一个,返回弹出的值
arr.unshift(1) // 前面增加
arr.shift() // 前面删除
arr.join(',') // 转成字符串,很有用
arr.slice(1, 3) // 数组按照序号截取[start,end) 
arr.splice(1, 1) // 从指定的index删除n个数据
const arr2 = arr.map(item => item * 2) // 映射
const arr3 = arr.filter(item => item<4) // 过滤
const item1 = arr.find(item => item<4) // 查找第一个
const itemIndex = arr.findIndex(item=> item===5) // 查询序号

// ===========去重=========
const arr4 = [...new Set(arr)] // 最简单去重,不过如果是判断对象内部属性去重就不能使用了
const arr5 = arr.reduce((res, item) => {
    !res.includes(item) && res.push(item)
    return res
}, []) // 用reduce函数去重可以自定义规则
arr.fill(0) // arr全部赋值成指定的值

// 伪数组转换数组
const arr6 = Array.prototype.slice.call(arguments, 0)
const arr7 = Array.from(arguments)
const arr8 = [...arguments]

对象深拷贝

前面使用的展开运算符或者Object.assign都是浅拷贝

如果要实现深层拷贝,需要自己实现或者用第三方库,比如lodashcloneDeep,一般都是递归复制对象,简单项目不想引入第三方库,也可以用JSON,不过需要注意JSON不支持嵌套对象互相引用。

   const a = {a:1,b:{c:2}}
   function simpleClone(obj){
       return JSON.parse(JSON.stringify(obj))
   }
   const d = simpleClone(a) // 深拷贝
   console.log(a.b===d.b) // false
   const e = {...a} // 浅拷贝
   console.log(a.b===e.b) // true

解构赋值

ES6中有个很重要的改变,就是结构赋值,相当方便

// 对象结构
const a = {b:1, c:2, d:3}
const {b,c} = a // 从a中结构出b和c
const {d: d1} = a // 从a中结构出d并修改名为d1,主要是在结构后重名的情况下使用
// 数组结构
const f = [1, 2, 3, 4]
const [g, h] = f // 解构f数组的0和1位置数据给g和h
let i = 1
let j = 2
// [i, j] = [j, i]  // 解构交换两个变量的值
function h(config){
    console.log(config.i, config.j)
}
h({i, j}) // 解构传参

function h1({i, j}){ // 参数直接解构
    console.log(i, j)
}
h1({i, j}) // 解构传参

注意:如果是Vue响应式数据要解构要谨慎,解构后可能不响应

Vue解构问题:https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#limitations-of-reactive

网页定时

网页定时常用的就是setInterval()setTimeout()

const handler1 = () => {
    console.log('setInterval')
}
const handler2 = () => {
    console.log('setTimeout')
    timer2=setTimeout(handler2, 1000)
}
const timer1 = setInterval(handler1, 1000)
let timer2 = setTimeout(handler2, 1000)
setTimeout(handler2) // 加入延时队列,异步执行
Promise.resolve().then(handler2) // 加入微队列,异步执行,Vue的nextTick

注意:定时器一般要在销毁的时候清理掉,尤其是单页应用要注意清理定时器

Promise

Promise是新的一步函数处理模式,ES6之前也有Promise A+规范,有不少第三方实现,比如jQuery也有自己的实现。

Promise实例

Promise是一个对象,有一个then方法,基本就可以认为是一个Promise对象了,支持async和await

// 新建Promise,then或catch等都返回一个新的Promise对象
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
})
  .then((data) => {
    console.log("then1", data);
    return 2;
  })
  .then((data) => {
    console.log("then2", data);
    return Promise.resolve(3);
  })
  .then((data) => {
    console.log("then3", data);
  })
  .then((data) => {
    console.log("then4", data);
  });
// promise.then方法有两个参数一个表示成功,一个表示失败
promise.then(successFunc, failFunc)
// 等价于
promise.then(successFun) // 成功
    .catch(failFunc) // 失败
    .finally((err) => console.log("finally", err)); // 无论成功或失败都调用

Promise静态方法

常用一些静态方法,用于操作或生成Promise对象实例

// 常用方法
Promise.resolve(1) // 生成直接完成Promise
Promise.reject(1) // 生成直接拒绝Promise
Promise.all([]) // 等待多个Promise,都成功才成功,失败一个就reject
Promise.allSettled([]) // 等待多个Promise,执行完,失败也包含在结果内
Promise.any([]) // 有一个成功就返回,都失败时拒绝

async和await

asyncawait是为了简化Promise开发,其实是语法糖,有一些注意事项

  1. await后面的代码类似于在then函数中执行,必须给函数添加async

    1. async function a(){
        console.log('开始执行执行1')
        const promise = new Promise(resolve=>{ // 生成Promise
            console.log('生成Promise2')
            setTimeout(()=>{
                resolve()
            }, 1000)
        })
        promise.then(()=>{
            console.log('then:等待1秒后执行3')
        })
        await promise
        console.log('await:等待1秒后执行4')
      }
      a()
  2. 加了async的函数无论原来返回什么,都是返回Promise对象了

    1. function a(){
       return 1 
      }
      async function b(){ // 打上async标记后,返回值就不再是1了
        return 1
      }
      function c(){
        return Promise.resolve(1)
      }
      async function d(){
        return Promise.resolve(1) // async函数如果已经返回Promise,就用返回的Promise
      }
      console.log(a())
      console.log(b())
      console.log(c()) // b和c函数基本等价
      console.log(d())
  3. 不是所有函数都支持被变成异步函数,有些直接依赖返回值的函数不能用async

    1. 作为回调函数需要注意一下是否支持返回Promise
  4. await异常处理,await遇到reject的话,将抛出异常

    1. async function a(){
        await Promise.reject(new Error('Test Error'))
          console.log('执行不到这里')
      }
      a()
      // 异常处理方式1
      async function b(){
        await Promise.reject(new Error('Test Error'))
          .catch(err=>console.log(err)) // catch之后正常执行,catch之后返回一个新的Promise,状态是fulfilled
          console.log('可以执行到这里')
      }
      b()
      // 异常处理方式2
      async function c(){
        try{
             await Promise.reject(new Error('Test Error'))
        }catch(err){
            console.log(err)
        }
          console.log('可以执行到这里')
      }
      c()
  5. 多个异步函数等待问题,如果业务支持并发等待,不要使用顺序await等待

    1. async function a(input){
        await new Promise((resolve)=>{
            setTimeout(function(){
                console.log(function a ${input})
                resolve()
            }, 3000)
        })
      }
      async function b(input){
        await new Promise((resolve)=>{
            setTimeout(function(){
                console.log(function b ${input})
                resolve()
            }, 3000)
        })
      }
      async function c(){
       await a('c')
       await b('c') // 顺序等待,是否是业务需要这样?
       console.log('c执行完成')
      }
      c()
      async function d(){
       const pa = a('d')
       const pb = b('d')
       // 并发等待
       // await Promise.all([pa, pb])
       await pa
       await pb
       console.log('d执行完成')
      }
      d()

模块化

ES6支持模块化代码,ES6之前,js代码在引入之后直接执行,产生的变量可能影响全局对象。

平时使用打包工具可能还会接触到nodejs的模块化

ES6模块

导出模块,假设文件为:./utils/index.js

export const a = '1' // 命名导出
const b = '2'
export { b as nb } // 导出重命名
export default { // 默认导出只能有一个
    c: '3'
}

上面导出对象结构类似:

{
    a: '1',
    nb: '2',
    default: {
        c: '3'
    }
}

导入模块:

import dayjs from 'dayjs' // 从库中导入default模块
import { createApp } from 'vue' // 命名导入,有点类似解构
import { a as na } from './utils' // 导入重命名
import * as allUtils from './utils' // 导入全部 allUtils.default为默认导出数据

const comp = import('./utils') // import函数异步导入,返回Promise

注意:尽量使用命名导入和导出,现代打包工具一般都支持treeshaking,没有用到的导出可以在打包的时候不打入构建结果中,减少js体积

Nodejs模块

NodeJS中使用CommonJS规范模块化,使用require导入,一般了解即可

导出模块:

const a = 1
module.exports = {
    a
}
module.exports.b = 2

导入模块:

const fs = require('fs') // 导入库
const utils = require('./utils') // 本地导入
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇