es6~es14常用语法和方法

579 阅读30分钟

cdn.jsdelivr.net/npm/eruda 调试用的移动端 console

目录结构图

image.png

一:let 变量

1):块作作用域

{
  let a = 10;
  var b = 1;
}

console.log(a) // ReferenceError: a is not defined.
console.log(b) // 1
for (let i = 0; i < 10; i++) {
}
console.log(i); // i is not defined
 
 
for (var j = 0; j < 10; j++) {
}
console.log(j); // 10

var a = [];
for (var i = 0; i < 2; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 2
a[1](); // 2
var a = [];
for (let i = 0; i < 2; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 0
a[1](); // 1

2):不存在变量提

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
console.log(foo); //报错
let foo = 2;

3): 暂时性死区 块级里面声明,就近原则查找

var num=10;
if(true){
  console.log(num) // 报错
  let num=2;
}
存在全局变量`num`,但是块级作用域内`let`又声明了一个局部变量`num`,导致后者绑定这个块级作用域,
所以在`let`声明变量前,对`num`赋值会报错。

4) 不重复声明

// 报错
function func() {
  let a = 10;
  let a = 1;
}

案例1:

image.png

2:案例:

image.png

image.png

二:const

1:特性

image.png

三:解构赋值

1:数组解构赋值

image.png

2:对象解构赋值

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

image.png

3:字符串解赋值

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5

4:案例

交换值

    let x=1;
    let y=2;
    [y,x]=[x,y];
    console.log(x,y)// 2 1

提取 JSON 数据*

    let my={
      name:'tao',
      age:18,
      job:'student'
    }
    let {name,age,job}=my;
    console.log(name,age,job) //tao 48 student

四:模板字符串

模板字符加方法

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

image.png

五:对象简写法

image.png

六:箭头函数

1;特性

a:简写:

** 简写1:**
    //es 5写法
    var fn=function (a,b){
      return a+b
    } 
    //es 6箭头函数
    var fun =(a,b)=>{
      return a+b
    } 

   // 调用 
    var fn=fun(1,2);
    console.log(fn)  //3
省略小括号,当形参只有一个时候
   let fn=function (a){
      return a+2
    } 
    // es6简写
    let fn=a=>{
       return a+2;
    }
省略小括号,当花括号只有一条语句时候
原来:
 let pow=n=>{
     return n*n
 }
省略花括号
 let pow=n=>return n*n
 此时 return 必须省略掉
 let pow=n=>n*n

// 案例一: es6:var f=v=>v; es5: function f(v){ retrun v }

b: 不能作为构造实例化对象

image.png

c:不能使用arguments 变量

image.png

2:案例

案例1:

image.png

案例2

image.png

image.png

七:函数参数赋值

image.png

image.png

八:symobol基本使用

symobo是 js语言的第七种数据类型,一种新的原始数据类型`Symbol`,表示独一无二的值;
前六种是:`undefined``null`、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object);

1:symbol 是唯一的,用来解决命名冲突问题
2:不能和其他数据进行运算
3:不能使用 for in  循环,但是可以使用 Reflect.ownKeys来获取对象的所有键名

数类型技巧:

    USONB    you are so niubility  //你很牛逼

    u:  undefined
    s:symbol  string 
    o :object
    n :null number
    b:boolean

1:创建

//创建方式一:
let s= Symbol();
console.log(s, typeof s)

let s1=Symbol('taotao')
let s2=Symbol('taotao')
console.log(s1===s2)// false

//创建方式二:
let s3=Symbol.for('tao')
let s4=Symbol.for('tao')
console.log(s3===s4) //ture

// 不能与其他数据运算,否则报错
let result=s+1;//报错
let result=s>1;
let result=s+2;

2:对象创建symbol类型属性

image.png

3 symbol内置属性

image.png

hasInstance:

image.png

isConcatSpreadable:

image.png

九:迭代器(iterator)

1:迭代器定义

主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

iterator是一种接口,为不同的数据结构提供统一的访问机制。任何数据结构只要部署iterator接口,就可以完成遍历操作。(for of:如数组array)

2:迭代器特点

1). es6创造了一种新的遍历命令 for...of循环。iterator接口主要提供for...消费

2). 原生的具备iterator接口的数据(可以直接用for..of遍历)

    a:Array
    b: Arguments
    c: Set
    d: Map
    e: String 
    f: TypedArray
    g: NodeList
let arr3 = [1, 2, 'kobe', true];
for(let i of arr3){
   console.log(i); // 1 2 kobe true
}
let str = 'abcd';
for(let item of str){
   console.log(item); // a b c d
}   
function fun() {
    for (let i of arguments) {
       console.log(i) // 1 4 5
    }
}
fun(1, 4, 5)
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit    

注意:普通的对象是不能被 for of 遍历的,直接食用会报错。

var obj = {};
    for (var k of obj) {
}

obj 不是可迭代的对象

image.png

如果想让obj对象具有这迭代功能,则要对象上部署 Symbol.iterator属性,然后为其创建一个迭代器方法如:下面案例 3)iterator自定义遍历对象 供for...of进行消费。

3). 工作原理

 a:创建一个指针对象,指向当前数据结构的起始位置
 b:第一次调用对象的next 方法,指针自动指向数据结构的第一个成员
 b:接下来不断调用 nxt方法,指针一直往后移动,直到指向最后一个成员
 d: 每次调用 next 方法放回一个包含value和done属性对象

注意:需要自定义遍历数据的时候,要想到迭代器

3:案例

1)array遍历【原生具有的数据】

image.png

image.png

2)iterator 的next方法

      var arry =['a','b','c','d','e'];
      let iterators= arry[Symbol.iterator]()
      console.log(iterators.next()) //{value: "a", done: false}
      console.log(iterators.next())  //{value: "b", done: false}
      console.log(iterators.next()) //{value: "c", done: false}
      console.log(iterators.next()) //{value: "d", done: false}
      console.log(iterators.next())//{value: "e", done: false}
      console.log(iterators.next())//{value: "undefined", done: true}

3)iterator自定义遍历对象 供for...of进行消费。

      const classes={
        name:'某某一班',
        classMate:[
          'xiaoming',
          'xiaoxing',
          'xiaoning',
          'xiaoyang',
        ],
        [Symbol.iterator](){
          let index=0;
          return{
            next:()=>{
              if(index<this.classMate.length){
                let result={value:this.classMate[index],done:false}
                index ++;
                return result
              }else{
                return {value:undefined,done:true}
              }
            }
          } 
        }
      }

      for(let item of classes){
        console.log(item) //xiaoming //xiaoxing //xiaoning //xiaoyang  一个个遍历打印出来
      }
function getIterator(list) {
    var i = 0;
    return {
        next: function() {
            var done = (i >= list.length);
            var value = !done ? list[i++] : undefined;
            return {
                done: done,
                value: value
            };
        }
    };
}
var it = getIterator(['a', 'b', 'c']);
console.log(it.next());// {value: "a", done: false}
console.log(it.next());// {value: "b", done: false}
console.log(it.next());// {value: "c", done: false}
console.log(it.next());// "{ value: undefined, done: true }"
console.log(it.next());// "{ value: undefined, done: true }"
console.log(it.next());// "{ value: undefined, done: true }"

4)arguments 类数组

函数内的arguments 是一个类数组,他也支持 for of,因为他内部也部署了 Iterator 接口。
我们都知道对象是默认没有部署这个接口的,所以arguments这个属性没有在原型上,而在在对象自身的属性上。

function test() {
    var obj = arguments[Symbol.iterator]("Symbol.iterator");
    console.log(arguments.hasOwnProperty(Symbol.iterator));
    console.log(arguments);
    console.log(obj.next());
}

test(1,2,3);

5)手写一个迭代器

    function myIterator(arr) {
        let nextIndex = 0
        return {
          next: function() {
            return nextIndex < arr.length
              ? { value: arr[nextIndex++], done: false }
              : { value: undefined, value: true }
          }
        }
      }
      let arr = [1, 4, 'ads']// 准备一个数据
      let iteratorObj = myIterator(arr)
      console.log(iteratorObj.next()) // 所有的迭代器对象都拥有next()方法,会返回一个结果对象
      console.log(iteratorObj.next())
      console.log(iteratorObj.next())
      console.log(iteratorObj.next())



![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/612a953234da406fbedfe82dce9de6be~tplv-k3u1fbpfcp-watermark.image)
总结来说,已默认部署 Iterator 接口的对象主要包括数组、字符串、SetMap 、类似数组的对象(比如 arguments 对象、DOM NodeList 对象。

## 4:拓展 
### 相关文章 https://juejin.cn/post/6844904025167495181
## 5:总结
`ES6`的出现带来了很多新的数据结构,比如 `Map` ,`Set` ,所以为了数据获取的方便,增加了一种统一获取数据的方式 `for of` 。而 `for of` 执行的时候引擎会自动调用对象的迭代器来取值。

不是所有的对象都支持这种方式,必须是实现了`Iterator`接口的才可以,这样的对象我们称他们为可迭代对象。

迭代器实现方式根据可迭代协议,迭代器协议实现即可。

除了统一数据访问方式,还可以自定义得到的数据内容,随便怎样,只要是你需要的。

迭代器是一个方法, 用来返回迭代器对象。

可迭代对象是部署了 `Iterator` 接口的对象,同时拥有正确的迭代器方法。

ES6 内很多地方都应用了`Iterator`,平时可以多留意观察,多想一步




十:生成器 Generator

1:定义:

  • Generator 函数是 ES6 提供的一种异步编程,异步回调 解决方案,语法行为与传统函数完全不同
  • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态
  • Generator 函数除了状态机,还是一个遍历器对象生成函数
  • 可暂停函数(惰性求值), yield可暂停,next方法可启动。每次返回的是yield后的表达式结果

2:特点

  • function关键字与函数名之间有一个星号;
  • 函数体内部使用yield表达式,定义不同的内部状态
 function* generatorExample(){
    console.log("开始执行")
    yield 'hello';  
    yield 'generator'; 
 }
// generatorExample() 
// 这种调用方法Generator 函数并不会执行
let MG = generatorExample() // 返回指针对象
MG.next() //开始执行  {value: "hello", done: false}

3 next传递参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

image.png

4.与 Iterator 接口的关系

普通对象 for of遍历报错,需求添加iteratrr接口属性

let obj = { username: 'kobe', age: 39 }
for (let i of obj) {
  console.log(i) //  Uncaught TypeError: obj is not iterable
}

改造:由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口

let obj = { username: 'kobe', age: 39 }
obj[Symbol.iterator] = function* myTest() {
  yield 1;
  yield 2;
  yield 3;
};
for (let i of obj) {
  console.log(i) // 1 2 3
}

方案1:直接为对象定义迭代器

const obj = { a: 1, b: 2 };
 
obj[Symbol.iterator] = function* () {
  for (const key of Object.keys(this)) {
    yield [key, this[key]]; // 返回键值对数组 
  }
};
 
// 遍历示例 
for (const [key, value] of obj) {
  console.log(key, value); // 输出:a 1 → b 2 
}

方案2:用生成器封装函数

function* objectEntries(obj) {
  for (const key of Object.keys(obj)) {
    yield { key, value: obj[key] }; // 返回结构化对象 
  }
}
 
const obj = { x: 10, y: 20 };
for (const entry of objectEntries(obj)) {
  console.log(entry); // 输出:{ key: 'x', value: 10 }, { key: 'y', value: 20 }
}

上面代码中,Generator函数赋值给Symbol.iterator属性,从而使得obj对象具有了 Iterator 接口,可以被for of遍历了

5.Generator的异步的应用

6.业务需求:

  • 发送ajax请求获取新闻内容
  • 新闻内容获取成功后再次发送请求,获取对应的新闻评论内容
  • 新闻内容获取失败则不需要再次发送请求。
    function* sendXml() {
      // url为next传参进来的数据
     let url = yield getNews('http://localhost:3000/news?newsId=2');//获取新闻内容
      yield getNews(url);//获取对应的新闻评论内容,只有先获取新闻的数据拼凑成url,才能向后台请求
    }
    function getNews(url) {
      $.get(url, function (data) {
        console.log(data);
        let commentsUrl = data.commentsUrl;
        let url = 'http://localhost:3000' + commentsUrl;
        // 当获取新闻内容成功,发送请求获取对应的评论内容
        // 调用next传参会作为上次暂停是yield的返回值
        sx.next(url);
      })
    }
    let sx = sendXml();// 发送请求获取新闻内容
    sx.next();

十一:ES6中的Set集合【重点】

1:定义

image.png

2:api

    // 声明
    let s=new Set()
    let s2=new Set([1,2,3,3,4,5,5,6,7,8,8,8,9])  //去重

    //增加
    s2.add(10);
    //删除
    s2.delete(4);
    // 检测 
    s2.has(0) // false

    // 遍历
    for(let i of s2){
      console.log(i)
    }

    console.log(s2)
      //清空
    s2.clear()

3:Set和普通数组关系【set用扩展运算符转普通数组】

    let s2=new Set(array)  //去重 是set,不是普通数组
    let newArray=[...s2] //转普通数组

4: 用for of 遍历 keys()values()entries()

  let s2=new Set([1,2,3,3,4,5,5,6,7,8,8,8,9])  //去重
   for(let i of s2){
      console.log(i) ////1,2,3,4,5,6,7,8,9
    }
      let s3=new Set(['中','国','人','民','解','放','军'])

      for(let i of s3.keys()){
          console.log(i) //'中','国','人','民','解','放','军'
      }
      for(let i of s3.values()){
          console.log(i) //'中','国','人','民','解','放','军'
      }
      for(let i of s3.entries()){
         console.log(i)//["中", "中"]
                      //  ["国", "国"]
                      //  ["人", "人"]
                      //  ["民", "民"]
                      //  ["解", "解"]
                      //  ["放", "放"]
                      //  ["军", "军"]
      }

6:遍历 foreach

let s3=new Set(['中','国','人','民','解','放','军'])
  s3.forEach((item)=>{
        console.log(item)
      })

7:案例

    let array=[1,2,3,3,4,5,5,6,7];

    // 去重
    let s2=new Set(array)  //去重 是set,不是普通数组
    let newArray=[...s2] //转普通数组
    console.log(newArray) //[1, 2, 3, 4, 5, 6, 7]

    let newaa=Array.from(s2) //转普通数组

    // 交集
     let array1=[5,6,7,8,8,8,9];
      //方法1:
     let result= newArray.filter(item=>{
        if(new Set(array1).has(item)){
          return true
        }else{
          return false
        }
     })
     console.log(result) //[5, 6, 7]

      //方法2:简洁
      let result2=newArray.filter(item=>new Set(array1).has(item))
      console.log(result2) //[5, 6, 7]


    //  并集
    let nuion=[...new Set([...array,...array1])]
    console.log(nuion) //[1, 2, 3, 4, 5, 6, 7, 8, 9]

    // 差集
    let array3=[5,6,7,8,8,8,9];
      // 注意:array和array3的差集
      let result3=newArray.filter(item=>!(new Set(array3).has(item)))
      console.log(result3) // [1, 2, 3, 4]

      // 注意:array3和array的差集
      let result4= [...new Set(array3)].filter(item=>!new Set(array).has(item))
      console.log(result4)  //[8, 9]

ES2025 为 Set 结构添加了以下集合运算方法。具体参考es6.ruanyifeng.com/#docs/set-m…

  • Set.prototype.intersection(other):交集
  • Set.prototype.union(other):并集
  • Set.prototype.difference(other):差集
  • Set.prototype.symmetricDifference(other):对称差集
  • Set.prototype.isSubsetOf(other):判断是否为子集
  • Set.prototype.isSupersetOf(other):判断是否为超集
  • Set.prototype.isDisjointFrom(other):判断是否不相交

十二:Map

1:定义:

image.png

2:api

  let m =new Map()

  // set 添加
  m.set('name','tao')
  m.set('change',function(){
    console.log('方法')
  })

  let key={
    name:'cesi'
  }
  m.set(key,['1','2','3'])

  // 长度
  console.log(m.size)// 3
  // 删除
  m.delete(key)

  //获取
  console.log(m.get('name'))
  console.log(m.get('change'))

  // 遍历
  for(let i of m){
    console.log(i)
  }

  // 清空
  m.clear()

遍历 for of keys()values()entries()

    let obj1={
      name:'wutao',
      age:18,
      say:'我是中国人民解放军'
    }
    let mapStr=new Map(Object.entries(obj1)) // 对象转map
    console.log(mapStr)

    for(let item of mapStr.keys()){
      console.log(item)
    }
    for(let item of mapStr.values()){
      console.log(item)
    }
    for(let item of mapStr.entries()){
      console.log(item)
    }
    for(let [key, value] of mapStr.entries()){
      console.log(key,value)
    }
    for(let [key, value] of mapStr){
      console.log(key,value)
    }

Map 数据结构的转换

    //数组 转为 Map
    let array=[      ['name', 'tao'],
      ['age',3],
      ['say','我是中国人民解放军']
    ]
    let map=new Map(array)
    console.log(map)
    // Map转数组
    let arry2=[...map]
    console.log(arry2)

let obj={name:'sds',age:'were'}
let map=new Map(Object.entries(obj))     // 对象转map
let newObj=Object.fromEntries(map) //map 转对象
// 对象转为 Map
    let obj1={
      name:'wutao',
      age:18,
      say:'我是中国人民解放军'
    }
    let mapStr=new Map(Object.entries(obj1)) // 对象转map
    let arry3=[...mapStr]
    console.log(arry3)
    

Object.keys()、 Object.values()、 Object.entries 、Object.fromEntries

set和map数据结构区别

  1. 初始化需要的值不一样,Map需要的是一个二维数组,是键值对的集合,而Set 需要的是一维 Array 数组是值的集合,
  2. Set的值是唯一的可以做数组去重,Map由于没有格式限制,可以做数据存储

以下是 JavaScript 中 Set 和 Map 的详细区别:

  1. 基本概念区别

Set - 值集合

// Set:存储唯一值的集合
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 重复值,不会被添加
set.add('hello');

console.log(set); // Set(3) { 1, 2, 'hello' }

Map - 键值对集合

// Map:存储键值对的集合
const map = new Map();
map.set('name', '张三');
map.set('age', 25);
map.set('age', 26); // 更新已存在的键

console.log(map); // Map(2) { 'name' => '张三', 'age' => 26 }
  1. 数据结构对比

特性 Set Map 存储内容 唯一值的集合 键值对的集合 元素形式 单个值 键值对 [key, value] 重复处理 自动去重 键唯一,值可重复 主要用途 去重、成员检查 键值映射、字典

  1. API 方法对比

添加元素

// Set - 使用 add()
const set = new Set();
set.add('apple');
set.add('banana');
set.add('apple'); // 重复,不会被添加

// Map - 使用 set()
const map = new Map();
map.set('fruit', 'apple');
map.set('color', 'red');
map.set('fruit', 'orange'); // 更新键 'fruit' 的值

获取元素

// Set - 没有直接的获取方法,只能检查存在性
const set = new Set(['a', 'b', 'c']);
console.log(set.has('a')); // true

// Map - 使用 get() 获取值
const map = new Map([['name', '张三'], ['age', 25]]);
console.log(map.get('name')); // "张三"
console.log(map.get('gender')); // undefined

共同的方法

const set = new Set([1, 2, 3]);
const map = new Map([['a', 1], ['b', 2]]);

// 共同的方法
console.log(set.size); // 3
console.log(map.size); // 2

console.log(set.has(2)); // true
console.log(map.has('a')); // true

set.delete(2);
map.delete('a');

set.clear();
map.clear();
  1. 迭代方式对比

Set 的迭代

const set = new Set(['apple', 'banana', 'orange']);

// 直接迭代值
for (const value of set) {
  console.log(value); // 'apple', 'banana', 'orange'
}

// 使用 values()(与直接迭代相同)
for (const value of set.values()) {
  console.log(value);
}

// 使用 forEach
set.forEach(value => {
  console.log(value);
});

// Set 的 keys() 和 values() 返回相同的迭代器
console.log(set.keys() === set.values()); // true

Map 的迭代

const map = new Map([
  ['name', '张三'],
  ['age', 25],
  ['city', '北京']
]);

// 迭代键值对
for (const [key, value] of map) {
  console.log(key, value); // 'name' '张三', 'age' 25, 'city' '北京'
}

// 分别迭代键和值
for (const key of map.keys()) {
  console.log(key); // 'name', 'age', 'city'
}

for (const value of map.values()) {
  console.log(value); // '张三', 25, '北京'
}

// 使用 entries()(与直接迭代相同)
for (const [key, value] of map.entries()) {
  console.log(key, value);
}

// 使用 forEach
map.forEach((value, key) => {
  console.log(key, value);
});
  1. 实际应用场景对比

Set 的应用场景

场景1:数组去重

// 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

// 字符串去重
const str = 'aabbccddee';
const uniqueChars = [...new Set(str)].join('');
console.log(uniqueChars); // 'abcde'

场景2:成员资格检查

// 权限检查
const adminPermissions = new Set(['create', 'update', 'delete', 'read']);

function checkPermission(permission) {
  return adminPermissions.has(permission);
}

console.log(checkPermission('create')); // true
console.log(checkPermission('approve')); // false

// 标签系统
const tags = new Set(['javascript', 'web', 'frontend']);

function addTag(tag) {
  if (tags.has(tag)) {
    console.log(`标签 "${tag}" 已存在`);
    return false;
  }
  tags.add(tag);
  return true;
}

场景3:数学集合操作

class SetOperations {
  // 并集
  static union(setA, setB) {
    return new Set([...setA, ...setB]);
  }

  // 交集
  static intersection(setA, setB) {
    return new Set([...setA].filter(x => setB.has(x)));
  }

  // 差集
  static difference(setA, setB) {
    return new Set([...setA].filter(x => !setB.has(x)));
  }

  // 对称差集
  static symmetricDifference(setA, setB) {
    return new Set([
      ...this.difference(setA, setB),
      ...this.difference(setB, setA)
    ]);
  }

  // 子集检查
  static isSubset(setA, setB) {
    return [...setA].every(x => setB.has(x));
  }
}

// 使用示例
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);

console.log(SetOperations.union(setA, setB)); // Set(4) {1, 2, 3, 4}
console.log(SetOperations.intersection(setA, setB)); // Set(2) {2, 3}
console.log(SetOperations.difference(setA, setB)); // Set(1) {1}

Map 的应用场景

场景1:对象映射

// 用户信息映射
const userMap = new Map();

userMap.set(1, { name: '张三', age: 25 });
userMap.set(2, { name: '李四', age: 30 });
userMap.set(3, { name: '王五', age: 28 });

// 快速查找
function getUserInfo(userId) {
  return userMap.get(userId) || { name: '未知用户', age: 0 };
}

console.log(getUserInfo(2)); // { name: '李四', age: 30 }

// 统计词频
function wordFrequency(text) {
  const frequencyMap = new Map();
  const words = text.toLowerCase().split(/\W+/);
  
  for (const word of words) {
    if (word) {
      frequencyMap.set(word, (frequencyMap.get(word) || 0) + 1);
    }
  }
  
  return frequencyMap;
}

const text = "hello world hello javascript world";
console.log(wordFrequency(text));
// Map(3) { 'hello' => 2, 'world' => 2, 'javascript' => 1 }

场景2:缓存系统

class SimpleCache {
  constructor(maxSize = 100) {
    this.cache = new Map();
    this.maxSize = maxSize;
  }

  set(key, value) {
    // 如果超过最大大小,移除第一个元素(FIFO)
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }

  get(key) {
    return this.cache.get(key);
  }

  has(key) {
    return this.cache.has(key);
  }

  delete(key) {
    return this.cache.delete(key);
  }

  clear() {
    this.cache.clear();
  }

  size() {
    return this.cache.size;
  }
}

// 使用示例
const cache = new SimpleCache(3);
cache.set('a', 1);
cache.set('b', 2);
cache.set('c', 3);
cache.set('d', 4); // 这会移除 'a'

console.log(cache.get('a')); // undefined
console.log(cache.get('d')); // 4

场景3:配置管理

class ConfigManager {
  constructor() {
    this.configMap = new Map();
    this.defaults = new Map();
  }

  setConfig(key, value, isDefault = false) {
    this.configMap.set(key, value);
    if (isDefault) {
      this.defaults.set(key, value);
    }
  }

  getConfig(key) {
    return this.configMap.get(key) ?? this.defaults.get(key);
  }

  resetToDefault(key) {
    if (this.defaults.has(key)) {
      this.configMap.set(key, this.defaults.get(key));
    }
  }

  getAllConfigs() {
    return Object.fromEntries(this.configMap);
  }

  // 批量设置配置
  setConfigs(configObject) {
    for (const [key, value] of Object.entries(configObject)) {
      this.setConfig(key, value);
    }
  }
}

// 使用示例
const config = new ConfigManager();

// 设置默认值
config.setConfig('theme', 'light', true);
config.setConfig('language', 'zh-CN', true);

// 设置用户配置
config.setConfig('theme', 'dark');
config.setConfig('fontSize', 14);

console.log(config.getConfig('theme')); // 'dark'
console.log(config.getConfig('language')); // 'zh-CN'

config.resetToDefault('theme');
console.log(config.getConfig('theme')); // 'light'
  1. 性能特点对比

查找性能

const array = Array.from({ length: 10000 }, (_, i) => i);
const set = new Set(array);
const map = new Map(array.map((x, i) => [i, x]));

const target = 5000;

// Array 查找
console.time('Array find');
array.includes(target);
console.timeEnd('Array find');

// Set 查找
console.time('Set has');
set.has(target);
console.timeEnd('Set has');

// Map 查找
console.time('Map get');
map.get(target);
console.timeEnd('Map get');

内存使用

// Set 和 Map 在存储大量数据时比 Object 更高效
const largeArray = Array.from({ length: 100000 }, (_, i) => i);

const obj = {};
largeArray.forEach(x => obj[x] = x);

const set = new Set(largeArray);
const map = new Map(largeArray.map(x => [x, x]));

// 在实际使用中,Set 和 Map 的内存效率通常更高
  1. 转换和互操作

与数组的转换

// Set 转数组
const set = new Set([1, 2, 3]);
const setToArray = [...set]; // [1, 2, 3]

// Map 转数组
const map = new Map([['a', 1], ['b', 2]]);
const mapToArray = [...map]; // [['a', 1], ['b', 2]]

// 数组转 Set
const arrayToSet = new Set([1, 2, 2, 3]); // Set(3) {1, 2, 3}

// 数组转 Map
const arrayToMap = new Map([['x', 10], ['y', 20]]);

Set 和 Map 之间的转换

// Map 的键转为 Set
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const keysSet = new Set(map.keys()); // Set(3) {'a', 'b', 'c'}

// Set 转为 Map(需要提供值)
const set = new Set(['x', 'y', 'z']);
const setToMap = new Map([...set].map(key => [key, true])); 
// Map(3) {'x' => true, 'y' => true, 'z' => true}
  1. 弱引用版本:WeakSet 和 WeakMap

WeakSet

// WeakSet 只能存储对象,且是弱引用
const weakSet = new WeakSet();
const obj1 = { id: 1 };
const obj2 = { id: 2 };

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1)); // true

// 当对象被垃圾回收时,会自动从 WeakSet 中移除

WeakMap

// WeakMap 的键必须是对象,且是弱引用
const weakMap = new WeakMap();
const keyObj = {};

weakMap.set(keyObj, 'private data');

console.log(weakMap.get(keyObj)); // 'private data'

// 常用于存储私有数据

总结

方面 Set Map 存储内容 唯一值 键值对 主要用途 去重、集合运算 映射、字典、缓存 查找方式 has(value) get(key) 添加元素 add(value) set(key, value) 重复处理 值唯一 键唯一 迭代内容 值 键值对 典型场景 标签、权限、去重 配置、缓存、映射关系

选择原则:

· 需要存储唯一值的集合 → 使用 Set · 需要键值映射关系 → 使用 Map · 需要高性能查找 → 两者都适合,比 Array 和 Object 更高效 · 需要弱引用 → 使用 WeakSet 或 WeakMap

十三:面向对象

function Foo(name) {
    let age = 25              // 私有属性
    function sex() {          // 私有方法
      console.log('male')
    }

    this.name = name          // 实例属性
    this.run = function() {   // 实例方法
      console.log('run')
    }
}

Foo.home = 'Beijing'        // 静态属性
Foo.talk = function() {     // 静态方法
    console.log('talk')
}

Foo.prototype.height = 180        // 原型属性
Foo.prototype.swim = function() { // 原型方法
    console.log('swim')
}

1:ES5面向对象(封装性、继承性(原型继承、组合式继承)、多态性)

1):面向对象的创建方法

A:字面量传统创建

image.png

B:构造函数创建

image.png

3):prototype和__proto__区别

image.png

4):原型属性和原型对象区别

image.png

    function Star(name,age){
      this.name=name
      this.age=age
    }
    Star.prototype.sing=function(){
      console.log('我会唱歌');
    }
    
    let ldh=new Star('刘德华','18')
    let zxy=new Star('张学友','18')

    // prototype 是构造函数 Star 的原型对象【属性与方法】

     ldh.sing()// 对象身上系统自己添加一个__proto__指向我们构造函数的原型对象
     
    console.log(ldh.__proto__ === Star.prototype) // true
    //方法规则:先看看ldh对象上是否有sing方法,如果有就执行这个对象的上的sing方法,
    //如果没有 sing方法,因为有__proto__的存在,就去构造函数原型对象Prototype身上查找 sing方法

5):对象原型和原型对象区别

image.png

6):总结原型对象,原型属性,对象原型 区别

image.png

  • 1:原型属性:相对构造函数person来说:prototype.say是 perosn的原型属性
  • 2:原型对象:相对构造函数实例出来P对象来说:protype是p对象的原型对象
  • 3:对象的原型:相对构造函数实例出来P对象来说:__proto__是p对象的对象原型

技巧:
protype是P对象原型对象,
__proto__是P对象的对象原型

7):构造器 constructor

image.png

1)作用1:原型对象和对象原型的 构造器一致:

image.png

2)作用2:构造函数多个属性(方法)

image.png

8):原型链

案例一:

image.png

案例二:

function Preson(){}   var p=new Preson()****

P-->Preson.prototype-->Object.prototype-->null

image.png

9):原型对象this指向

image.png

this指向应用:内置对象添加自定义方法

image.png

10):call方法

作用1:调用函数

image.png

作用2:改变this指向

image.png

11):继承性(es5组合式继承)

纯属继承普通属性

image.png

继承父方法和属性

image.png

内置对象原型链

a:系统内置对象的连****

image.png

b:构造函数创建对象的原型连****

image.png

image.png

image.png

image.png


2:Es6面向对象

ES5

image.png

image.png

ES6

语法糖(syntactic sugar)

是指编程语言中可以更容易的表达一个操作的语法,它可以使程序员更加容易去使用这门语言:操作可以变得更加清晰、方便,或者更加符合程序员的编程习惯。

image.png

使用extends关键字

    class Father{ // 父类
    }
    class Son extends Father{ //子类继承父类

    }

使用super关键字

super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数

super如何调用普通函数
 console.log(super.say())
 super.say() 就是调用父类中的普通函数                                                            
super注意事项:

image.png

注意:子类在构造函数中使用super,必须放到this之前(必须先调用父类的构造方法,再使用子类构造方法)

1.在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象。

2.类里面的共有属性和方法一定要加this调用。

3.类里面的this指向问题 constructor里面的this指向的是 创建的实例对象,方法里面的this指向这个方法的调用者

class的 get 和set

image.png

十四: 对象扩展方法

Object.is 判断两个值是否相等

  console.log(Object.is(1,1)) // true
  console.log(Object.is('sdfdsf','sdfdsf')) // true
  console.log(Object.is(NaN,NaN))// true
  console.log(NaN===NaN)// false  Object is 和 === 区别 就是NaN 

Object.assign 对象合并

    let Obj={
      name:'Obj',
      age:10,
      jum:function(){
        console.log('跳舞z')
      }
    }
    let Obj1={
      name:'Obj1',
      age:13,
      say:function(){
        console.log('say')
      }
    }

    console.log(Object.assign(Obj,Obj1))
    //{
          name:'Obj1',
          age:13,
          say:function(){
            console.log('say')
          },
          jum:function(){
            console.log('跳舞z')
          }
      }
    

Object.setPrototypeOf 改变对象原型

    let school={
      name:'sdf'
    }
    let array=['1','2','3']
    Object.setPrototypeOf(school,array)
    Object.setPrototypeOf(school,array)
    console.log(school.__proto__) //["1", "2", "3"] 

十五: 模块化exprot、import

模块功能主要两个命令 export import构成 exprot 命令用于规定模块的对外接口 import 命令用于输入其他模块提供的功能

1):export 语法汇总

// 分别暴露
  // export let phone="手机品牌"
  // export function call(){
  //   console.log('打电话')
  // }

// 统一暴露
  // let phone="手机品牌"
  // function call(){
  //  console.log('打电话')
  // }
  // export {phone,call};


// 默认暴露
  // 写法一:
    // export default {
    //   iphone:"手机",
    //   call:function(){
    //     console.log('打电话')
    //   }
    // }
    

  // 写法二:
    // let obj={
    //   iphone:"手机",
    //   call:function(){
    //     console.log('打电话')
    //   }
    // }
    // export default obj

image.png

2):import 语法汇总

// 1:通用导入方式
  // import * as m1 from '@/assets/js/m1.js'
  // import * as m2 from '@/assets/js/m2.js'
  // import * as m3 from '@/assets/js/m3.js'
      //console.log(m3.default)


//2:解构导入方式

  //import { phone,call} from '@/assets/js/m1.js' // m1是分别暴露  
  //console.log(phone,call())

  //import { phone as mobaile ,call} from '@/assets/js/m2.js' // m2统一暴露 
  //console.log(mobaile,call()) 
  // 注意:由于 phone 跟上面 m1,phone命名冲突,可以用别名 phone as mobaile
  
  //import { default as m3} from '@/assets/js/m3.js' //m3 默认暴露
  //console.log(m3)
  
  
//3:简便方式: 只针对默认暴露方式
  //import m3 from '@/assets/js/m3.js'

image.png

3):为了减少代码的体积直接把那些导入方式的js,统一放到一个入口js文件,让后在需要的文件引入入口文件即可

image.png

十六:export default 和export 和 module.exports 和 exports 区别

module.exports和exports是属于 CommonJS 模块规范,export和export default是属于ES6语法。
module.exports和exports导出模块,用require引入模块。
export和export default导出模块,import导入模

1): export和 exprot default

在创建JavaScript模块时,export 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。被导出的绑定值依然可以在本地进行修改。在使用import进行导入时,这些绑定值只能被导入模块所读取,但在export导出模块中对这些绑定值进行修改,所修改的值也会实时地更新。

  • 1: 导出实时绑定的函数、对象或原始值,其他程序通过import 使用
  • 2: 绑定的值只能被导入模块读取
  • 3:export导出模块中对这些绑定值进行修改,所修改的值也会实时地更新
如:a.js文件下

export function getCookie(keys) {
    var values = '';
    var cookie = document.cookie;
    var cookie_arr = cookie.split(";");
    for (var i = 0; cookie_arr[i]; i++) {
        var c_arr = cookie_arr[i].split('=');
        var _key = c_arr[0].replace(' ', '');
        if (_key == keys) {
            values = c_arr[1];
            break;
        }
    }
    return values;
}

b.js文件导入使用 
import {getCookie} from "@/config/utils.js";
 

两种 export 导出方式:

命名导出(每个模块包含任意数量)
默认导出(export defaul每个模块包含一个)

// 导出单个特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}

// 导出列表
export { name1, name2, …, nameN };

// 重命名导出(别名)
export { variable1 as name1, variable2 as name2, …, nameN };

// 解构导出并重命名
export const { name1, name2: bar } = o;

// 默认导出
export default expression;
export default function () { … } // also class, function*
export default function name1() { … } // also class, function*
export { name1 as default, … };

// 导出模块合集
export * from …; // does not set the default export
export * as name1 from …; // Draft ECMAScript® 2O21
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
在导出多个值时,命名导出非常有用。在导入期间,必须使用相应对象的相同名称。

但是,可以使用任何名称导入默认导出,例如:

// 文件 test.js  
let k; export default k = 12;   
// 另一个文件  
import m from './test'; // 由于 k 是默认导出,所以可以自由使用 import m 替代 import k  
console.log(m);        // 输出为 12   
 你也可以重命名命名导出以避免命名冲突:  

export { myFunction as function1,  
         myVariable as variable };  

例子

// childModule1.js 中
let myFunction = ...; // assign something useful to myFunction
let myVariable = ...; // assign something useful to myVariable
export {myFunction, myVariable};


// childModule2.js 中
let myClass = ...; // assign something useful to myClass
export myClass;


// parentModule.js 中
// 仅仅聚合 childModule1 和 childModule2 中的导出
// 以重新导出他们
export { myFunction, myVariable } from 'childModule1.js';
export { myClass } from 'childModule2.js';


// 顶层模块中
// 我们可以从单个模块调用所有导出,因为 parentModule 事先
// 已经将他们“收集”/“打包”到一起
import { myFunction, myVariable, myClass } from 'parentModule.js'

区别:

export default是唯一的,导出没有{},如export default a,导入:import a from export可以是多个,且要加{},如export {a,b},导入:import {a,b} from

2): module.exports 和exports区别

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即 module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令。

var exports = module.exports;
exports其实是module.exports的引用 ,可以直接在exports对象上添加相关的方法。

1)使用module.exports导出模块:
新建一个文件demo.js,通过module.exports输出变量x和函数add。

var x = 1;
var add = function (val) {
  return val + x;
};
module.exports.x = x;
module.exports.add = add;


(2)使用require引入模块
require方法用于加载模块。
var demo = require('./demo.js');
console.log(demo.x); // 1
console.log(demo.add(1)); // 6

例子

1:a.js文件中方法导出

function formatTime(date) {
  var year = date.getFullYear()
  var month = date.getMonth() + 1
  var day = date.getDate()

  var hour = date.getHours()
  var minute = date.getMinutes()
  var second = date.getSeconds()


  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
module.exports = {
  formatTime: formatTime
}


2:b.js把a.js引入并使用

 var Util = require('../a.js');
 Util.formatTime() //调用


 

区别:

(1)module.exports和exports的用法是后面加一个等号,再接具体的导出

module.exports=... exports=...

十七:使用 Es6-bable对Es6模块化的代码转换与引入Npm包

为了兼容普通浏览器对es6语法兼容

image.png

2:)es6 引入npm 包:如jq

image.png

十八:Es7新特性

1):es7:includes 数组元素是否存在

    let array=['你好','哈喽','嗨']
    array.includes('哈喽')// true
    
    const array1 = [1, 2, 3];
    console.log(array1.includes(2, 2)); // false, 因为元素2在索引1的位置,而includes从索引2开始查找,所有找不到返回false
   
    与indexof 区别就是 indexOf如果不存在返回下标;否则返回-1
    其比较法是[零值相等算法],即 [-0].includes(+0) 返回 true,
    但是NaN可以正常比较[NaN].includes(NaN) 返回 true

2):es7 **幂运算

    console.log(2 ** 10)//1024
    console.log(Math.pow(2,10))//1024//等价

拓展 improtant 和 important from区别

如果 imporant b form 'aaa.css' b是undefined,则form引入是没有意义的,所以 直接写imporant 'aaa.css'

十九:ES8新特性

1): Es8: async函数

特性:

  • 1:async 函数的返回值为promise对象
  • 2:promise对象的结果由函数执行返回值决定

代码:

案例1: 返回结果不是一个promise对象【返回的是字符串】,async返回的结果则是一个成功的promise

image.png

案例2:抛出错误,async返回的结果是一个失败的promise

image.png

案例3:如果返回的结果是一个promise的对象,则async返回的结果就是promise返回的结果

image.png

2): Es8: await 表达式

特性:

  • 1:await 必须写在async 函数中,但是async 可以没有await
  • 2:await右侧的表达式一般为promise对象
  • 3:await 返回的是pormise成功的值
  • 4:await 的promise失败了,就会抛出异常,需要通过try...catch捕获处理

案例1:await 返回的是pormise成功的值

image.png

案例2:await 的promise失败了,就会抛出异常,需要通过try...catch捕获处理

image.png

3:)ES8 async函数活 await表达式结合

可以让异步代码,像同步代码一样,一起返回

案例:多个ajax请求,可以让异步代码,像同步代码一样,一起返回结果

image.png

image.png

fuction sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

// ES8 之前 使用Promise.then
sleep(1000).then(() => console.log('hello world'))

// ES8 之后 使用async/await
async function asyncPrint(value, ms) {
  await sleep(ms)
  console.log(value)
}
asyncPrint('hello world', 50)

4)总结:异步方法

promise / 迭代器interator / 生成器 generator / ansync await

二十:ES8对象扩展方法 Object.keys、Object.values、Object.entries

Object.keys、Object.values、Object.entries

    let newobject={
      name:'tao',
      city:['湛江','廉江','深圳'],
      kemu:['前端','后台']
    }
    // 获取对象的所有键
    console.log(Object.keys(newobject)) 
    //["name", "city", "kemu"]
    // 获取对象的所有值
    console.log(Object.values(newobject)) 
    //["tao", :['湛江','廉江','深圳'], ['前端','后台']]

    //  entries
    console.log(Object.entries(newobject)) 
    //[        // ['name','tao'],
        // ['city',['湛江','廉江','深圳']],
        //['kemu',['前端','后台']]
    //]
    
    //创建Map
     const m =new Map(Object.entries(sho)) 
     console.log(m) // [{'name'=>'tao'},'citys'=>['湛江','廉江','深圳']]
     
     Object.getOwnPropertyDescriptors  // 对象属性的描述对象
    

Object.getOwnPropertyDescriptors 对象属性的描述对象

image.png

二十一:es9扩展运算符与rest参数

image.png

image.png

es9 for await...of

  • for await...of语句创建一个循环,该循环会等待每个Promise对象完成,然后使用对象的结果继续。例如,当处理多个Promise对象时,for await...of语句可以等待所有Promise对象完成,然后继续执行。
async function* asyncGenerator() {
  yield 1;
  yield 2;
}

(async function() {
  for await (const x of asyncGenerator()) {
    console.log(x); // 1 2
  }
})();

es9 Promise.finally()方法用于在Promise对象完成(无论是成功还是失败)后执行一个回调函数。例如,可以使用Promise.finally()方法来清理资源或执行一些清理操作。

let promise = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Success');
    }, 1000);
  });
}

promise()
  .then(result => {
    console.log(result); // 'Success'
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    console.log('Promise completed');
    // 清理loading \ 重置状态啥的
  });

// 一般我们使用 try catch finally 
try {
  // some asynchronous operation
  await promise();
} catch (error) {
  console.error(error);
} finally {
  console.log('Promise completed');
}

二十二:es10对象扩展方法 Object.formEntries方法

参数:二维数组、map

image.png

image.png

image.png

es10 fromEntries和 es8的Object.entries区别

二维数组和对象相互转换

image.png

  // fromEntries //二维数组转对象  和 es8 entries相互转换
    // 二维数组转对象
    const res=Object.fromEntries([
      ['name','名字'],
      ['age','18'],
      ['funArray',['1','2','3']]
    ])
    console.log('fromEntries_res',res) //{age: "18",name: "名字",funArray:['1','2','3']}
    // 将对象转二维数组
    const res2=Object.entries(res)
    console.log('entries_re',res2)

    // 将二维数组转Map对象
    const Mapres=new Map(res2) //{'name' => '名字', 'age' => '18', 'funArray' => Array(3)}
    console.log(Mapres)

二十三: es10的 trimStrat 与 trimEnd 和es5的 trim区别

    let Str=' 哈喽 '
    
    console.log(Str.trim()) // es5 方法 前后字符串清除空格
    console.log(Str.trimStart()) // es10 清除字符串前空格
    console.log(Str.trimEnd()) // es10 清除字符串后面空格

二十四:es10 flat和flatMap 数组扩展方法

flat降维方法: 参数就是深度,降几个层级数组

    let array2=[1,2,3,4,5,[6,7,8,9]] 
    console.log(array2.flat()) //[1,2,3,4,5,6,7,8,9]    将二维数组转一维数组

    let array3=[1,2,3,[4,5,6,[7,8,9]]];
    console.log(array3.flat()) // [1,2,3,4,5,6,[7,8,9]]  将三维数组转二维维数组
    
    console.log(array3.flat(2)) //[1,2,3,4,5,6,7,8,9] 将三维数组转一维数组

flatMap

    let aray=[1,2,3];
    let result= aray.map(item=> item * 10 ) // 数组每个元素乘以10
    console.log(result) //[10, 20, 30]
    let aray2=[[4],[5],[6]];
    let result2= aray2.flatMap(item=> [item * 10] )
    console.log(result2) // [40, 50, 60]

二十五: es11 私有属性

image.png

image.png

二十六:es11 动态important

image.png

BigInt:表示任意大的整数

js安全数:-(2^53-1)到(2^53-1)

let bigInt = 1234567890123456789012345678901234567890n;
let bigInt1 = BigInt('1234567890123456789012345678901234567890');
console.log(bigInt); // 1234567890123456789012345678901234567890n

Promise.allSettled

  • 与 Promise.all区别 用法完全一致; 如果全部结果为成功==》等效 Promise.all
  • 返回值:一个Promise, Promise兑现的是参数中所有promise的结果组成的数组(无论成功、失败),并且是按顺序返回
let promise1 = Promise.resolve('1');
let promise2 = Promise.reject('2');
const allRes = Promise.allSettled([promise1, promise2]);
allRes.then((res) => {
  console.log(res); // [
//   { status: 'fulfilled', value: '1' },
//   { status: 'rejected', reason: '2' }
// ]
});


Nullish coalescing operator

  • ??:当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
let a = null;
let b = undefined;
let c = 0;
let d = '';
let e = NaN;
let g = 1;
let h = 'hello';

console.log(a ?? b); // undefined
console.log(a ?? c); // 0
console.log(a ?? d); // ''
console.log(a ?? e); // NaN
console.log(a ?? g); // 1
console.log(a ?? h); // hello

console.log(a || b); // undefined
console.log(a || c); // 0
console.log(a || d); // ''
console.log(a || e); // NaN
console.log(a || g); // 1
console.log(a || h); // hello

  • 注意与|| 的区别
console.log(0 ?? null); // 0
console.log(0 ?? 1); // 0
console.log(3 ?? 1); // 3

console.log(0 || null); // null
console.log(0 || 1); // 1


可选链 ?.- 可选链用于访问深层对象属性或者函数调用,即时引用不存在也不会报错,而是返回undefined。

const obj = {
  name: '小易',
  age: 18,
  info: {
    gettAge() {
      return this.age;
    },
    gender: '男',
    address: {
      city: '北京',
      street: '长安街',
      country: '中国',
    },
  },
}

console.log(obj?.name); // 小易
console.log(obj?.info?.gender); // 男
console.log(obj?.info?.address?.city); // 北京
console.log(obj?.info?.address?.street); // 长安街
console.log(obj?.info?.address?.country); // 中国
console.log(obj?.xx?.address?.country); // undefined
console.log(obj?.info?.getAge?.()); // 18
console.log(obj?.info?.getAge?.()); // undefined


globalThis

ES12(2021)

  • String.prototype.replaceAll
  • Promise.any
  • WeakRef: 对象允许你保留对另一个对象的弱引用,但不会阻止垃圾回收(GC)清理被弱引用的对象。
  • 数字分隔符

- 使用下划线(_)作为数字分隔符,使数字更易读。

let num = 1_000_000_000;
console.log(num); // 1000000000


ES13(2022)

  • Top-level await 顶层 await
  • Arrary.at
  • Object.hasOwn
  • Class Fields #作为class私有属性、简写法

Top-level await

  • 在模块的顶层作用域使用await,不再需要将await放在async函数中。
const response = await fetch('https://api.example.com/data');

// 以往可以使用函数嵌套
(async function() {
    const response = await fetch('https://api.example.com/data');
})();

Arrary.at

  • Array.at():返回指定索引处的元素,如果索引为负数,则从数组末尾开始计算。
const arr = [1, 2, 3, 4, 5];
console.log(arr.at(2)); // 3
console.log(arr.at(-1)); // 5

Object.hasOwn

  • Object.hasOwn():用于判断对象自身是否具有指定的属性,而不是从原型链上查找。

  • 建议使用此方法替代 Object.prototype.hasOwnProperty(),因为它适用于使用 Object.create(null) 创建的对象,以及重写了继承的 hasOwnProperty() 方法的对象。

const obj = { a: 1, b: 2 };
console.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(obj, 'c')); // false

const obj2 = Object.create(null);
obj2.a = 1;
console.log(Object.hasOwn(obj2, 'a')); // true
console.log(Object.hasOwn(obj2, 'b')); // false
console.log(obj2.hasOwnProperty('a')); // TypeError: obj2.hasOwnProperty is not a function

Class 简写法

class Person {
  age = 18;  // 原型属性默认值
}
let person = new Person();
person.age; // 18
Person.name; // 'Person'

ES14(2023)

  • Arrary.findLast、findLastIndex()

  • 新增几个不会影响原数组,并返回新数组的方法

    • Array.prototype.toReversed() -> Array
    • Array.prototype.toSorted(compareFn) -> Array
    • Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
    • Array.prototype.with(index, value) -> Array

findLast、findLastIndex

  • findLast():从数组的最后一个元素开始,查找满足条件的元素,并返回第一个匹配的元素。
  • findLastIndex():从数组的最后一个元素开始,查找满足条件的元素,并返回第一个匹配的元素的索引。
const arr = [1, 2, 3, 4, 5];
const lastEven = arr.findLast((value) => value % 2 === 0);
console.log(lastEven); // 4

const lastEvenIndex = arr.findLastIndex((value) => value % 2 === 0);

新增数组方法

不会影响原数组,并返回新数组的方法,用法和原来一样

  • toReversed():返回一个新数组,该数组是原数组的反转。

  • toSorted(compareFn):返回一个新数组,该数组是原数组的排序。

  • toSpliced(start, deleteCount, ...items):返回一个新数组,该数组是原数组的部分替换。

  • with(index, value):返回一个新数组,该数组是原数组的部分替换。

const arr = [1, 2, 3, 4, 5];
const reversedArr = arr.toReversed(); // [5, 4, 3, 2, 1]
const sortedArr = arr.toSorted();  // [1, 2, 3, 4, 5]
const splicedArr = arr.toSpliced(2, 1, 10);  // [1, 2, 10, 4, 5]
const withArr = arr.with(2, 10);  // [1, 2, 10, 4, 5]

二十七:es6扩展符

 // 案例二:
 var o1={id:1}
 var o2={name:'tao'}
 var o3={...o1,o2} // {id:1,o2:{name:'tao'}}
 var o4={...o1,...o2} // {id:1,name:'tao'}

----------------总结知识点--------------------

二十八: JavaScript 中 forEach、map、filter 详细

1、forEach 和 map 能实现的功能相似
2、forEach 、 mapfilter 都能实现对原数组的修改
3、forEach 没有返回值,map 有返回值,filter 有返回值

1):forEach

a):没有返回值

var arr1 = [1, 2, 3, 4, 5]

var solt = arr1.forEach((v,i,t) => {
  console.log(v,i,t)
})
console.log(solt)// undefined
// 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]

b):不能中止或跳出 forEach 循环

var arr1 = [1, 2, 3, 4, 5]

// 使用break会报错
arr1.forEach((v,i,arr) => {
  console.log(v)
  if(v === 3) {
    break
  }
})

// return false 也无效
arr1.forEach((v,i,arr) => {
  console.log(v)
  if(v === 3) {
    console.log('-----')
    return false
  }
})
// 1
// 2
// 3
// -----
// 4
// 5

c):对原数组进行修改

image.png

arr1 从 [1, 2, 3] 变成了 [2, 4, 6]  函数内部 this 值是 arr2

2): es5数组方法map()

map() 方法创建一个数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果

1、map 不修改调用它的原数组本身

['1', '2', '3'].map( str => {
  parseInt(str)
})


['1', '2', '3'].map(Number);  // [1, 2, 3]
// 与 parseInt 不同,下面的结果会返回浮点数或指数:
['1.1', '2.2e2', '3e300'].map(Number);  // [1.1, 220, 3e+300]

3):filter

filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素

var filtered = [12, 5, 8, 130, 44].filter(function(v) {
  return v >= 10
})
// [12, 130, 44]

参考链接:juejin.cn/post/684490…

二十九:字符串新增的方法

1):ES6实例方法:includes(), startsWith(), endsWith() 

  • includes() :返回布尔值,表示是否找到了参数字符串。
  • startsWith() :返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith() :返回布尔值,表示参数字符串是否在原字符串的尾部。 这三个方法都支持第二个参数,表示开始搜索的位置。
    let  str="sfsdfsdaf"
    console.log(str.includes('g'))//false
    console.log(str.startsWith('s'))//true
    console.log(str.endsWith('f'))//true

这三个方法都支持第二个参数,表示开始搜索的位置。

let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

2):ES7:padStart(),padEnd()

padStart()padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。

 let str1="1";
      console.log(str1.padStart(2,'0'))  //01 
      console.log(str1.padEnd(2,'Y'))  //1Y

image.png

3):Es10的 trimStrat 与 trimEnd 和es5的 trim

    let Str=' 哈喽 '
    
    console.log(Str.trim()) // es5 方法 前后字符串清除空格
    console.log(Str.trimStart()) // es10 清除字符串前空格
    console.log(Str.trimEnd()) // es10 清除字符串后面空格

三十:函数的扩展

es5:的逻辑中断:
function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

Es6:改造

function log(x, y='world') {
  
  console.log(x, y);
}
log()

箭头函数

更短的语法

基础语法如下:

(参数)=> { statements }
复制代码

接下来,拆解一下箭头函数的各种书写形式:

当没有参数时,使用一个圆括号代表参数部分

let f = ()=> 5;

f(); // 5
复制代码

当只有一个参数时,可以省略圆括号。

let f = num => num + 5;

f(10); // 15
复制代码

当有多个参数时,在圆括号内定义多个参数用逗号分隔。

let f = (a,b) => a + b;

f(1,2); // 3
复制代码

当箭头函数的代码块部分多余一条语句,就需要使用大括号括起来,并且使用 return 语句。

// 没有大括号,默认返回表达式结果
let f1 = (a,b) => a + b
f1(1,2) // 3

// 有大括号,无return语句,没有返回值
let f2 = (a,b) => {a + b}
f2(1,2) // undefined

// 有大括号,有return语句,返回结果
let f3 = (a,b) => {return a + b}
f3(1,2) // 3
复制代码

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

//报错
let f1 = num => {num:num}

//不报错
let f2 = num => ({num:num})

箭头函数碰上 call、apply、bind

箭头函数根本没有自己的 this ,所以:

  • 当对箭头函数使用 call 或 apply 方法时,只会传入参数并调用函数,并不会改变箭头函数中 this 的指向;
  • 当对箭头函数使用 bind 方法时,只会返回一个预设参数的新函数,并不会绑定新函数的 this 指向。
window.name = 'window_name';

let f1 = function(){return this.name}
let f2 = ()=> this.name

let obj = {name:'obj_name'}

f1.call(obj) // obj_name
f2.call(obj) // window_name

f1.apply(obj) // obj_name
f2.apply(obj) // window_name

f1.bind(obj)() // obj_name
f2.bind(obj)() // window_name

自执行函数

报错:
(() => { console.log(1) }())

经典面试题:

image.png

三十一:数组的扩展【重点】

1):扩展运算符

数组展开

console.log(...[1, 2, 3])
// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

复制数组【克隆】

  let arry1=[1,2,3]
  let arry2=[...arry1];
  let arry3=arry1;
  console.log(arry2=== arry1)//false
  console.log(arry3=== arry1)//true

合并数组

    方法一:
    let arry1=[1,2,3]
    let arry2=[4,5,6]
    let arry3=[...arry1,...arry2]
    console.log(arry3)// [1, 2, 3, 4, 5, 6]
    
    方法二:
     let arry1=[1,2,3]
    let arry2=[4,5,6]
    arry1.push(...array2]
    
    
    

与解构赋值结合

    let arry1=[1,2,3]
    let [a,b]=['a',arry1]
    console.log(a,b) //a  [1, 2, 3]

字符串转数组

console.log([...'hello']) //["h", "e", "l", "l", "o"]

Generator 函数运行后,返回一个遍历器对象

const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

Map 和 Set 结构混合使用

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

2):Array.form

array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

类似数组对象

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

let arrayLike = {
    '0': '1',
    '1': '2',
    '2': '3',
    length: 3
};
var =Array.from(arrayLike,item=>item*2) [2,4,6]

dom 组合

let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

Iterator 接口的数据结构 Set 和Map

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

3) Array.of()

Array.of()方法用于将一组值,转换为数组。

  Array.of(3, 11, 8) // [3,11,8]
  Array.of(3) // [3]
  Array.of(3).length // 1

注意:是字符串,还是数组与逗号隔开

      let str1='a,b,c,d,e';
      console.log(Array.of(str1)) //["a,b,c,d,e"]
      console.log(Array.of(1,2,3))//[1,2,3]

三十二: set和Map区别【重点】

----------------总结常见面试题--------------------

let

{
  let a = 10;
  var b = 1;
}

console.log(a) // ReferenceError: a is not defined.
console.log(b) // 1
for (let i = 0; i < 10; i++) {
}
console.log(i); // i is not defined
 
 
for (var j = 0; j < 10; j++) {
}
console.log(j); // 10

var a = [];
for (var i = 0; i < 2; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 2
a[1](); // 2
var a = [];
for (let i = 0; i < 2; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[0](); // 0
a[1](); // 1

箭头函数

  var obj={
    age:20,
    say:()=>{
      alert(this.age)
    }
  }
  obj.say() // 弹窗是undefined  
  // 因为obj是一个对象,不能产生作用域,箭头函数被定义全局作用域下,所有say里的this指向的是window
      var age=100
      var obj={
        age:20,
        say:()=>{
          alert(this.age)
        }
      }
      obj.say() // 弹窗100  
      // 因为obj是一个对象,不能产生作用域,箭头函数被定义全局作用域下,所有say里的this指向的是window
var a = 10
var obj = {
  a: 20,
  say: function() {
    console.log(this.a)
  }
}
obj.say.call(this) //10 此处显示绑定this为全局window对象
var a = 10
var obj = {
  a: 20,
  say: function() {
    console.log(this.a)
  }
}
obj.say() // 20 // 
var b='ces';
let a='aa'
var aa=setTimeout(function(){
console.log(this.a)// undefined
console.log(this.b)// ces
},1000)


var b='ces';
let a='a'
var dd=setTimeout(()=>{
console.log(this.a)// undefined
console.log(this.b)// 'ces'
},1000)


分析:回调函数的this都指向windowlet声明的变量不指向window,而 var是声明是全局变量


var obj = {
  value:0,
  fn:function(){
    var f = function(){
      this.value ++
    }
    f();
  }
}
obj.fn();
console.log(obj.value); // 0
函数 f 中的 this 并不是 方法 obj.fn 中的 this,导致我们没法获取到 obj.value 。

修改箭头函数后
var obj = {
  value:0,
  fn:function(){
    var f = ()=>{
      this.value ++
    }
    f();
  }
}
obj.fn();
console.log(obj.value); // 1


let obj = {
    fn:function(){
        console.log('我是普通函数',this === obj)
        return ()=>{
            console.log('第一个箭头函数',this === obj)
            return ()=>{
                console.log('第二个箭头函数',this === obj)
                return ()=>{
                    console.log('第三个箭头函数',this === obj)
                }
            }
        }
    }
}

console.log(obj.fn()()()())
// 我是普通函数 true
// 第一个箭头函数 true
// 第二个箭头函数 true
// 第三个箭头函数 true

箭头函数面试题

function foo(n) {
  var f = () => arguments[0] + n;
  return f();
}

let res = foo(2);

console.log(res); // 4

function A() {
  this.foo = 1
}

A.prototype.bar = () => console.log(this.foo)

let a = new A()
a.bar() // undefined  es6箭头函数 this 执行是windows

let res = (function pt() {
  return (() => this.x).bind({ x: 'inner' })();
}).call({x: 'outer' });

console.log(res)  // outer

解析:
1:拆分
let pt=function (){
  return (()=>this.x).bind({x:'inner'})()
}

pt.call({x:'outer'})

// call执行 pt函数并传{x:'outer'}参数
   return 语句是自动执行个箭头函数 undefined 所以外层查找 
  (() => this.x).bind({ x: 'inner' })();
   箭头函数中的 this 无法通过 bind 方法绑定,箭头函数执行时的 this 就是外层作用域的 this。
  
  
  
  官方解析:
  
-   求函数 pt 通过 call 调用后的返回值。

-   pt 函数内的 this 被 call 转换为 {x:'outer'}。

-   pt 函数内,箭头函数通过 bind 生成了新函数,并执行,执行结果为 pt 函数的返回值。

-   箭头函数中的 this 无法通过 bind 方法绑定,箭头函数执行时的 this 就是外层作用域的 this。

-   箭头函数执行时,外层作用域的 this 是由 call 方法指定的 {x:'outer'}。

-   最终结果 res 为 'outer'
window.name = 'window_name';

let obj1 = {
    name:'obj1_name',
    print:()=>console.log(this.name)
}

let obj2 = {name:'obj2_name'}

obj1.print()  //   window_name
obj1.print.call(obj2)  //   window_name
此题中,箭头函数外层没有普通函数,所以 this 指向全局对象,所以结果为

let obj1 = {
    name:'obj1_name',
    print:function(){
        return ()=>console.log(this.name)
    }
}

let obj2 = {name:'obj2_name'}


obj1.print()() // 问 输出结果
obj1.print().call(obj2) // 问 输出结果
obj1.print.call(obj2)() // 问 输出结果
答案: 'obj1_name' 'obj1_name' 'obj2_name'

箭头函数的 this 与其外层的普通函数的 this 一致,与 call、apply、bind 无关。

此题,obj1.print 返回一个箭头函数,此箭头函数中的 this 就是 obj1.print 调用时的 this。

obj1.print()():此时obj1.print 中的 this 为 obj1,所以输出为 obj1_name
obj1.print().call(obj2):此时obj1.print 中的 this 为 obj1,所以输出为 obj1_name
obj1.print.call(obj2)():此时obj1.print 中的 this 为 obj2,所以输出为 obj2_name

image.png

Es6箭头函数推荐文章:juejin.cn/post/684490…

扩展运算符

对象合并

方法一:
 let bar = { a: 1, b: 2 }; 
 let baz = { ...bar }; // { a: 1, b: 2 }
    
 方法二:
 let bar = { a: 1, b: 2 }; 
 let baz = Object.assign({}, bar); // { a: 1, b: 2 }

数组合并

    方法一:
    let arry1=[1,2,3]
    let arry2=[4,5,6]
    let arry3=[...arry1,...arry2]
    console.log(arry3)// [1, 2, 3, 4, 5, 6]
    
    方法二:
     let arry1=[1,2,3]
    let arry2=[4,5,6]
    arry1.push(...array2]

三十三 class 面向对象 和es5面向对象区别

//基本

    // es5 构造函数
      // function Es5Phone(name,price){
      //   this.name=name
      //   this.price=price
      // }
      // Es5Phone.prototype.call=function(){
      //    console.log('会打电话e')
      // }
      // let es5laorenji=new Es5Phone('老人机',100)
      // console.log(es5laorenji)



    // es6
      // class Es6Phone{
      //   constructor (name,price){
      //     this.name=name;
      //     this.price=price
      //   }
      //   call(){
      //     console.log('会打电话e')
      //   }
      // }
      // let laorenji=new Es6Phone('老人机',100)
      // console.log(laorenji)



// 构造函数静态成员  实例对象不能返回函数对象的属性

  //e5
      // function Es5Phone2(){
      // }
      // Es5Phone2.name1 ='静态成员';
      // let es5laorenji2=new Es5Phone2()
      // console.log(es5laorenji2.name1) // undefined  
      // console.log(Es5Phone2.name1) // 静态成员  



  // es6
    //  class Phone{
    //     static name ='静态成员'
    //     static call(){
    //       console.log('静态方法')
    //     }
    //   }

    //    let es6laorenji2=new Phone()
    //   console.log(es6laorenji2.name) // undefined  
    //   console.log(Phone.name) // 静态成员  

    

//继承
    // es5
  //   function Phone(brand,price){
  //     this.brand=brand
  //     this.price=price;
  //   }
  //   Phone.prototype.callFun=function(){
  //     console.log('会打电话e')
  //   }
    

  //   function SmartPhone(brand,price,color,size){
  //     Phone.call(this,brand,price)
  //     this.color=color
  //     this.size=size
  //   }
  //   // 设置子级函数的原型
  //   SmartPhone.prototype= new Phone;
  //   SmartPhone.prototype.constructor=SmartPhone;
  //   SmartPhone.prototype.photo=function(){
  //     console.log('我会拍照')
  //   }

  //   let xiaomi=new SmartPhone('小米','1999','白色','5.5')
  //   let nokai= new Phone('诺基亚','199')
    
  // console.log(nokai,xiaomi)


  //es6
  class Phone{
    constructor(brand,price){
      this.brand=brand;
      this.price=price
    }
    // 父类成员
    callFun(){
      console.log('会打电话e')
    }
  }

  class SmartPhone extends Phone {
    constructor(brand,price,color,size){
      super(brand,price) // 相当于es5 的 Pone.call(this,brand,price)
      this.color=color;
      this.price=price;
    }
    callFun(){
      console.log('我会视频电话,和打电话')
    }
    photo(){
      console.log('我会拍视频,拍照')
    }
  }
  
  let xiaomi=new SmartPhone('小米','1999','白色','5.5')
  let nokai= new Phone('诺基亚','199')
  console.log(nokai,xiaomi)

-----------注意的区别和混淆------------------

1: Array.of和 Array.form

注意:Array.of 是字符串,还是数组与逗号隔开

      let str1='a,b,c,d,e';
      console.log(Array.of(str1)) //["a,b,c,d,e"]
      console.log(Array.of(1,2,3))//[1,2,3]

注意:Array.from 和 Array.of区别

      let str1='a,b,c,d,e';
      console.log(Array.of(str1)) //["a,b,c,d,e"]

      console.log(Array.from(str1)) //["a", ",", "b", ",", "c", ",", "d", ",", "e"]

2:es9 fromEntries 二维数组转对象 和 es8 entries相互转换 和es6 二维数组转Map对象

  // fromEntries //二维数组转对象  和 es8 entries相互转换
  
    // 二维数组转对象
    const res=Object.fromEntries([
      ['name','名字'],
      ['age','18'],
      ['funArray',['1','2','3']]
    ])
    console.log('fromEntries_res',res) //{age: "18",name: "名字",funArray:['1','2','3']}
    // 将对象转二维数组
    const res2=Object.entries(res)
    console.log('entries_re',res2)

    // 将二维数组转Map对象
    const Mapres=new Map(res2) //{'name' => '名字', 'age' => '18', 'funArray' => Array(3)}
    console.log(Mapres)

具体区分哪年新出的可以查看参考该链接https://juejin.cn/post/7399862413234782242