ES6~ES13新特性(一)

文章目录

一、ES6中对象的增强

1.字面量的增强

ES6中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。

字面量的增强主要包括下面几部分:

  • 属性的简写:Property Shorthand

  • 方法的简写:Method Shorthand

  • 计算属性名:Computed Property Names

javascript 复制代码
/*
      1.属性的增强
      2.方法的增强
      3.计算属性名的写法
*/

var name = "why"
var age = 18

var key = "address" + " city"

var obj = {
    // 1.属性的增强
    name,
    age,

    // 2.方法的增强
    running: function() {
        console.log(this)
    },
    swimming() {
        console.log(this)
    },
    eating: () => {
        console.log(this)
    },

    // 3.计算属性名
    [key]: "广州"
}

obj.running()
obj.swimming()
obj.eating()

function foo() {
    var message = "Hello World"
    var info = "my name is why"

    return { message, info }
}

var result = foo()
console.log(result.message, result.info)

2.解构Destructuring

ES6中新增了一个从数组或对象中方便获取数据的方法,称之为解构Destructuring。

解构赋值 是一种特殊的语法,它使我们可以将数组或对象拆包至一系列变量中。

我们可以划分为:数组的解构对象的解构

数组的解构:

  • 基本解构过程
  • 顺序解构
  • 解构出数组:...语法
  • 默认值

对象的解构:

  • 基本解构过程
  • 任意顺序
  • 重命名
  • 默认值
javascript 复制代码
var names = ["abc", "cba", undefined, "nba", "mba"]


// 1.数组的解构
// var name1 = names[0]
// var name2 = names[1]
// var name3 = names[2]
// 1.1. 基本使用
// var [name1, name2, name3] = names
// console.log(name1, name2, name3)

// 1.2. 顺序问题: 严格的顺序
// var [name1, , name3] = names
// console.log(name1, name3)

// 1.3. 解构出数组
// var [name1, name2, ...newNames] = names
// console.log(name1, name2, newNames)

// 1.4. 解构的默认值
var [name1, name2, name3 = "default"] = names
console.log(name1, name2, name3)

// 2.对象的解构
var obj = { name: "why", age: 18, height: 1.88 }
// var name = obj.name
// var age = obj.age
// var height = obj.height
// 2.1. 基本使用
// var { name, age, height } = obj
// console.log(name, age, height)

// 2.2. 顺序问题: 对象的解构是没有顺序, 根据key解构
// var { height, name, age } = obj
// console.log(name, age, height)


// 2.3. 对变量进行重命名
// var { height: wHeight, name: wName, age: wAge } = obj
// console.log(wName, wAge, wHeight)

// 2.4. 默认值
var { 
    height: wHeight, 
    name: wName, 
    age: wAge, 
    address: wAddress = "中国"
} = obj
console.log(wName, wAge, wHeight, wAddress)

// 2.5. 对象的剩余内容
var {
    name,
    age,
    ...newObj
} = obj
console.log(newObj)

3.解构的应用场景

解构目前在开发中使用是非常多的:

  • 比如在开发中拿到一个变量时,自动对其进行解构使用;

  • 比如对函数的参数进行解构;

javascript 复制代码
// 应用: 在函数中(其他类似的地方)
function getPosition({ x, y }) {
    console.log(x, y)
}

getPosition({ x: 10, y: 20 })
getPosition({ x: 25, y: 35 })

二、手写实现apply、call、bind方法

1.apply、call方法

javascript 复制代码
// new Function()
// foo.__proto__ === Function.prototype
function foo(name, age) {
    console.log(this, name, age)
}

// foo函数可以通过apply/call
// foo.apply("aaa", ["why", 18])
// foo.call("bbb", "kobe", 30)

// 1.给函数对象添加方法: hyapply
Function.prototype.hyapply = function(thisArg, otherArgs) {
    // this -> 调用的函数对象
    // thisArg -> 传入的第一个参数, 要绑定的this
    // console.log(this) // -> 当前调用的函数对象
    // this.apply(thisArg)

    thisArg.fn = this

    // 1.获取thisArg, 并且确保是一个对象类型
    thisArg = (thisArg === null || thisArg === undefined)? window: Object(thisArg)

    // thisArg.fn = this
    Object.defineProperty(thisArg, "fn", {
        enumerable: false,
        configurable: true,
        value: this
    })
    thisArg.fn(...otherArgs)

    delete thisArg.fn
}

// foo.hyapply({ name: "why" }, ["james", 25])
// foo.hyapply(123, ["why", 18])
// foo.hyapply(null, ["kobe", 30])


// 2.给函数对象添加方法: hycall
Function.prototype.hycall = function(thisArg, ...otherArgs) {
    // 1.获取thisArg, 并且确保是一个对象类型
    thisArg = (thisArg === null || thisArg === undefined)? window: Object(thisArg)

    // thisArg.fn = this
    Object.defineProperty(thisArg, "fn", {
        enumerable: false,
        configurable: true,
        value: this
    })
    thisArg.fn(...otherArgs)

    delete thisArg.fn
}

foo.hycall({ name: "why", fn: "abc" }, "james", 25)
foo.hycall(123, "why", 18)
foo.hycall(null, "kobe", 30)

抽取代码进行封装

javascript 复制代码
// new Function()
// foo.__proto__ === Function.prototype
function foo(name, age) {
    console.log(this, name, age)
}

// foo函数可以通过apply/call
// foo.apply("aaa", ["why", 18])
// foo.call("bbb", "kobe", 30)

// 1.封装思想
// 1.1.封装到独立的函数中
function execFn(thisArg, otherArgs, fn) {
    // 1.获取thisArg, 并且确保是一个对象类型
    thisArg = (thisArg === null || thisArg === undefined)? window: Object(thisArg)

    // thisArg.fn = this
    Object.defineProperty(thisArg, "fn", {
        enumerable: false,
        configurable: true,
        value: fn
    })

    // 执行代码
    thisArg.fn(...otherArgs)

    delete thisArg.fn
}

// 1.2. 封装原型中
Function.prototype.hyexec = function(thisArg, otherArgs) {
    // 1.获取thisArg, 并且确保是一个对象类型
    thisArg = (thisArg === null || thisArg === undefined)? window: Object(thisArg)

    // thisArg.fn = this
    Object.defineProperty(thisArg, "fn", {
        enumerable: false,
        configurable: true,
        value: this
    })
    thisArg.fn(...otherArgs)

    delete thisArg.fn
}


// 1.给函数对象添加方法: hyapply
Function.prototype.hyapply = function(thisArg, otherArgs) {
    this.hyexec(thisArg, otherArgs)
}
// 2.给函数对象添加方法: hycall
Function.prototype.hycall = function(thisArg, ...otherArgs) {
    this.hyexec(thisArg, otherArgs)
}

foo.hyapply({ name: "why" }, ["james", 25])
foo.hyapply(123, ["why", 18])
foo.hyapply(null, ["kobe", 30])

foo.hycall({ name: "why" }, "james", 25)
foo.hycall(123, "why", 18)
foo.hycall(null, "kobe", 30)

2.bind方法

javascript 复制代码
// apply/call
function foo(name, age, height, address) {
    console.log(this, name, age, height, address)
}

// Function.prototype
// var newFoo = foo.bind({ name: "why" }, "why", 18)
// newFoo(1.88)

// 实现hybind函数
Function.prototype.hybind = function(thisArg, ...otherArgs) {
    // console.log(this) // -> foo函数对象
    thisArg = thisArg === null || thisArg === undefined ? window: Object(thisArg)
    Object.defineProperty(thisArg, "fn", {
        enumerable: false,
        configurable: true,
        writable: false,
        value: this
    })

    return (...newArgs) => {
        // var allArgs = otherArgs.concat(newArgs)
        var allArgs = [...otherArgs, ...newArgs]
        thisArg.fn(...allArgs)
    }
}

var newFoo = foo.hybind("abc", "kobe", 30)
newFoo(1.88, "广州市")
newFoo(1.88, "广州市")
newFoo(1.88, "广州市")
newFoo(1.88, "广州市")

三、ECMA新描述概念

1.新的ECMA代码执行描述

在执行学习JavaScript代码执行过程中,我们学习了很多ECMA文档的术语:

  • 执行上下文栈:Execution Context Stack,用于执行上下文的栈结构;

  • 执行上下文:Execution Context,代码在执行之前会先创建对应的执行上下文;-

  • 变量对象:Variable Object,上下文关联的VO对象,用于记录函数和变量声明

  • 全局对象:Global Object,全局执行上下文关联的VO对象;

  • 激活对象:Activation Object,函数执行上下文关联的VO对象;

  • 作用域链:scope chain,作用域链,用于关联指向上下文的变量查找;

在新的ECMA代码执行描述中(ES5以及之上),对于代码的执行流程描述改成了另外的一些词汇

  • 基本思路是相同的,只是对于一些词汇的描述发生了改变;

  • 执行上下文栈和执行上下文也是相同的;

2.词法环境(Lexical Environments)

词法环境是一种规范类型,用于在词法嵌套结构中定义关联的变量函数等标识符;

  • 一个词法环境是由环境记录(Environment Record)和一个外部词法环境(oute;r Lexical Environment)组成;

  • 一个词法环境经常用于关联一个函数声明、代码块语句、try-catch语句,当它们的代码被执行时,词法环境被创建出来;

也就是在ES5之后,执行一个代码,通常会关联对应的词法环境;

  • 那么执行上下文会关联哪些词法环境呢?

3.词法环境和变量环境

LexicalEnvironmentVariableEnvironment

  • LexicalEnvironment用于存放letconst声明的标识符:
  • VariableEnvironment用于存放varfunction声明的标识符:

4.环境记录(Environment Record)

在这个规范中有两种主要的环境记录值:声明式环境记录和对象环境记录。

声明式环境记录 :声明性环境记录用于定义ECMAScript语言语法元素的效果,如函数声明变量声明和直接将标识符绑定与ECMAScript语言值关联起来的Catch子句。

对象式环境记录:对象环境记录用于定义ECMAScript元素的效果,例如WithStatement,它将标识符绑定与某些对象的属性关联起来。

四、let、const的使用

1.let、const基本使用

在ES5中我们声明变量都是使用的var关键字,从ES6开始新增了两个关键字可以声明变量:letconst

let关键字

从直观的角度来说,letvar是没有太大的区别的,都是用于声明一个变量

const关键字

const关键字是constant的单词的缩写,表示常量、衡量的意思;

它表示保存的数据一旦被赋值,就不能被修改

但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容;

注意:另外let、const不允许重复声明变量;

javascript 复制代码
// ES6之前
var message1 = "Hello World"
message1 = "Hello Coderwhy"
message1 = "aaaaa"
console.log(message1)

// ES6开始
// 1.let
let message2 = "你好, 世界"
message2 = "你好, why"
message2 = 123
console.log(message2)

// 2.const
// const message3 = "nihao, shijie"
// message3 = "nihao, why"

// 赋值引用类型
const info = {
    name: "why",
    age: 18
}
// info = {}
info.name = "kobe"
console.log(info)

let、const声明的变量不允许重复声明

javascript 复制代码
// 1.var变量可以重复声明
// var message = "Hello World"
// var message = "你好, 世界"


// 2.let/const不允许变量的重复声明
// var address = ""
let address = "广州市"
// let address = "上海市"
const info = {}
// const info = {}

2.let、const作用域提升

let、const和var的另一个重要区别作用域提升

我们知道var声明的变量是会进行作用域提升的,但是如果我们使用let声明的变量,在声明之前访问会报错

那么是不是意味着foo变量只有在代码执行阶段才会创建的呢?

事实上并不是这样的,我们可以看一下ECMA262对let和const的描述;

这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值;

从上面我们可以看出,在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的。

那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢?

事实上维基百科并没有对作用域提升有严格的概念解释,那么我们自己从字面量上理解;

作用域提升 :在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升;

在这里,它虽然被创建出来了,但是不能被访问,我认为不能称之为作用域提升

所以我的观点:

let、const没有进行作用域提升,但是会在解析阶段被创建出来。

3.let、const声明变量保存位置

我们知道,在全局通过var来声明一个变量,事实上会在window上添加一个属性:

但是let、const是不会给window上添加任何属性的。

那么我们可能会想这个变量是保存在哪里呢?

我们先回顾一下最新的ECMA标准中对执行上下文的描述:

也就是说我们声明的变量和环境记录是被添加到变量环境中的:

但是标准有没有规定这个对象是window对象或者其他对象呢?

其实并没有,那么JS引擎在解析的时候,其实会有自己的实现;

比如v8中其实是通过VariableMap的一个hashmap来实现它们的存储的。

那么window对象呢?而window对象是早期的GO(Global Object)对象,在最新的实现中其实是浏览器添加的全局对象,并且一直保持了window和var之间值的相等性

4.块级作用域

在我们前面的学习中,JavaScript只会形成两个作用域:全局作用域函数作用域

ES5中放到一个代码中定义的变量,外面是可以访问的:

javascript 复制代码
{
    var a = 10;
}
console.log(a)

在ES6中新增了块级作用域 ,并且通过letconstfunctionclass声明的标识符是具备块级作用域的限制的:

javascript 复制代码
{
    var a = "hello";
    let info = "zhan";
    const num = 10;
    class Person {}
}
console.log(a) // 可以访问
console.log(info) // 无法访问
console.log(Person) // 无法访问

但是我们会发现函数拥有块级作用域,但是外面依然是可以访问的:

  • 这是因为引擎会对函数的声明进行特殊的处理,允许像var那样进行提升

4.1暂时性死区

暂时性死区指在作用域内执行开始位置到标识符定义这块区域

javascript 复制代码
// 1.暂时性死区
// function foo() {
//   console.log(bar, baz)

//   console.log("Hello World")
//   console.log("你好 世界")
//   let bar = "bar"
//   let baz = "baz"
// }
// foo()

// 2.暂时性死区和定义的位置没有关系, 和代码执行的顺序有关系
// function foo() {
//   console.log(message)
// }

// let message = "Hello World"
// foo()
// console.log(message)

// 3.暂时性死区形成之后, 在该区域内这个标识符不能访问
let message = "Hello World"
function foo() {
    console.log(message)

    const message = "哈哈哈哈"
    }

foo()

块级作用域的应用场景

html 复制代码
<body>
  <div>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
  </div>
  <script>
const btnArr = document.querySelectorAll("button");
for (let i = 0; i < btnArr.length; i++) {
  btnArr[i].onclick = function () {
    console.log(`按钮${i+1}`);
  }
}
    </script>

5.var、let、const的选择

那么在开发中,我们到底应该选择使用哪一种方式来定义我们的变量呢?

对于var的使用:

  • 我们需要明白一个事实,var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题

  • 其实是JavaScript在设计之初的一种语言缺陷

  • 当然目前市场上也在利用这种缺陷出一系列的面试题,来考察大家对JavaScript语言本身以及底层的理解;

  • 但是在实际工作中,我们可以使用最新的规范来编写,也就是不再使用var来定义变量了;

对于letconst

  • 对于let和const来说,是目前开发中推荐使用的;
  • 我们会有限推荐使用const,这样可以保证数据的安全性不会被随意的篡改;
  • 只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let;
  • 这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范;

五、模板字符串

1.字符串模板基本使用

在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)。

ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:

首先,我们会使用```````` 符号来编写字符串,称之为模板字符串;

其次,在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容;

javascript 复制代码
const age = 10
const info = `吃饭睡觉打豆豆的,${age} 是我的年龄`

2.标签模板字符串的使用

模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)。

我们一起来看一个普通的JavaScript的函数:

javascript 复制代码
function foo(...args) {
    console.log(args)
}
foo("hello world")

如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:

javascript 复制代码
const name = "hello"
const age = 18
foo`abcd ${age} is ${name}`

模板字符串被拆分了,第一个元素是数组,是被模块字符串拆分的字符串组合;后面的元素是一个个模块字符串传入的内容;

2.1标签模板字符串的应用

React的styled-components库用法

六、函数知识补充

1.函数的默认参数

在ES6之前,我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求:

  • 传入了参数,那么使用传入的参数;

  • 没有传入参数,那么使用一个默认值;

而在ES6中,我们允许给函数一个默认值:

javascript 复制代码
function foo(x="name", y="age") {
    console.log(x, y)
}
foo()// name, age

默认值也可以和解构一起来使用:

javascript 复制代码
function foo({name, age}) {
    console.log(name, age)
}
// 方式1
function foo({name, age} = {name: "zhangsan", age: 18}) {
    console.log(name, age)
}
// 方式2
function foo({name = "zhangsan", age = 18} = {}) {
    console.log(name, age)
}

另外参数的默认值我们通常会将其放到最后(在很多语言中,如果不放到最后其实会报错的):

但是JavaScript允许不将其放到最后,但是意味着还是会按照顺序来匹配;

另外默认值会改变函数的length的个数,默认值以及后面的参数都不计算在length之内了。

javascript 复制代码
// 注意: 默认参数是不会对null进行处理的
function foo(arg1 = "我是默认值", arg2 = "我也是默认值") {
    // 1.两种写法不严谨
    // 默认值写法一:
    // arg1 = arg1 ? arg1: "我是默认值"

    // 默认值写法二:
    // arg1 = arg1 || "我是默认值"

    // 2.严谨的写法
    // 三元运算符
    // arg1 = (arg1 === undefined || arg1 === null) ? "我是默认值": arg1

    // ES6之后新增语法: ??
    // arg1 = arg1 ?? "我是默认值"

    // 3.简便的写法: 默认参数
    console.log(arg1)
}

foo(123, 321)
foo()
foo(0)
foo("")
foo(false)
foo(null)
foo(undefined)

2.函数的剩余参数

ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:

如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;

javascript 复制代码
function foo(m, n, ...args) {
    console.log(m, n)
    console.log(args)
}

那么剩余参数和arguments有什么区别呢?

  • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参;
  • arguments对象不是一个真正的数组,而rest参数是一个真正的数组,可以进行数组的所有操作;
  • arguments是早期的ECMAScript中为了方便去获取所有的参数提供的一个数据结构,而rest参数是ES6中提供并且希望以此来替代arguments的;
  • 剩余参数必须放到最后一个位置,否则会报错。

4.箭头函数的补充

在前面我们已经学习了箭头函数的用法,这里进行一些补充:

  • 箭头函数是没有显式原型的,所以不能作为构造函数,使用new来创建对象;
javascript 复制代码
var foo = () => {}
console.log(foo.prototype)//undefined
var f1 = new foo()// TypeError: foo is not a constructor
javascript 复制代码
// 1.function定义的函数是有两个原型的:
function foo() {}
console.log(foo.prototype) // new foo() -> f.__proto__ = foo.prototype
console.log(foo.__proto__) // -> Function.prototype

// 2.箭头函数是没有显式原型
// 在ES6之后, 定义一个类要使用class定义
var bar = () => {}
console.log(bar.__proto__ === Function.prototype)//false
// 没有显式原型
console.log(bar.prototype)
var b = new bar()

七、其它知识补充

1.展开语法

展开语法(Spread syntax):

  • 可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;
  • 还可以在构造字面量对象时, 将对象表达式按key-value的方式展开;

展开语法的场景:

  • 在函数调用时使用;
  • 在数组构造时使用;
  • 在构建对象字面量时,也可以使用展开运算符,这个是在ES2018(ES9)中添加的新特性;

注意:展开运算符其实是一种浅拷贝;

javascript 复制代码
// 1.基本演练
// ES6
const names = ["abc", "cba", "nba", "mba"]
const str = "Hello"

// const newNames = [...names, "aaa", "bbb"]
// console.log(newNames)

function foo(name1, name2, ...args) {
    console.log(name1, name2, args)
}

foo(...names)
foo(...str)

// ES9(ES2018)
const obj = {
    name: "why",
    age: 18
}
// 不可以这样来使用
// foo(...obj) // 在函数的调用时, 用展开运算符, 将对应的展开数据, 进行迭代
// 可迭代对象: 数组/string/arguments

const info = {
    ...obj,
    height: 1.88,
    address: "广州市"
}
console.log(info)

2.数值的表示

在ES6中规范了二进制和八进制的写法:

javascript 复制代码
const foo = 0x100; // 十六进制
const bar = 100; // 十进制
const ten = 0o100; // 八进制
const bin = 0b100; // 二进制

另外在ES2021新增特性:数字过长时,可以使用_作为连接符

javascript 复制代码
const num = 100_000_000;

八、symbol

1.symbol的基本使用

Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号

那么为什么需要Symbol呢?

  • 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
  • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
  • 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
  • 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;

Symbol就是为了解决上面的问题,用来生成一个独一无二的值

Symbol值是通过Symbol函数来生成的,生成后可以作为属性名

也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;

Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;

我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;

2.Symbol作为属性名

我们通常会使用Symbol在对象中表示唯一的属性名

javascript 复制代码
const s1 = Symbol();

// 方式1
const obj = {}
obj[s1] = "zhangsan"

// 方式2
Object.defineProperty(obj, s1, {
    value: "lisi"
})

// 方式3
const obj2 = {
    [s1]: "wangwu"
}

获取对象的symbol属性名

javascript 复制代码
// 获取symbol对应的key
console.log(Object.keys(obj))
console.log(Object.getOwnPropertySymbols(obj))
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const key of symbolKeys) {
    console.log(obj[key])
}

3.相同值的Symbol

前面我们讲Symbol的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol应该怎么来做呢?

我们可以使用Symbol.for方法来做到这一点;

javascript 复制代码
const key1 = Symbol.for("abc");
const key2 = Symbol.for("abc");
console.log(key1 === key2);//true

并且我们可以通过Symbol.keyFor方法来获取对应的key;

javascript 复制代码
Symbol.keyFor(key1)// abc
相关推荐
CodeClimb5 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
光头程序员7 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
fmdpenny8 小时前
Vue3初学之商品的增,删,改功能
开发语言·javascript·vue.js
小美的打工日记8 小时前
ES6+新特性,var、let 和 const 的区别
前端·javascript·es6
涔溪9 小时前
有哪些常见的 Vue 错误?
前端·javascript·vue.js
程序猿online9 小时前
前端jquery 实现文本框输入出现自动补全提示功能
前端·javascript·jquery
Turtle11 小时前
SPA路由的实现原理
前端·javascript
HsuYang11 小时前
Vite源码学习(九)——DEV流程中的核心类(下)
前端·javascript·架构
傻小胖11 小时前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
蓝冰凌13 小时前
【整理】js逆向工程
javascript·js逆向