【2024秋第4节课】Javascript核心解密(ES6)

343 阅读13分钟
第四节课11.png

Javascript核心解密(ES6)

这节课的内容有一些多,但是又很重要,建议大家多抽出时间来学习😁

课程之前

在你学习这部分内容时,我希望你已经学习 :

  1. JS的6个数据类型
  2. 数组的常用方法
  3. 选择,循环结构以及函数的相关知识

详情可见上上次课的课件



那么我们开始今天的学习🙌🙌🙌🙌🙌🙌

首先我们知道,

JS的组成主要分为三部分:ECMAScript(核心),DOM(文档对象模型),BOM(浏览器对象模型)。 所以在接下来的时间里面我们就会按照这个顺序依次来学习这三个部分。

什么是ES6?

先来解决第一个问题,ES6是什么,
ES6ECMAScript 6.0是简称,其实它在2015年6月就发布出来了,所以又称ES2015,目的是使JavaScript这一门语言可以来编写更加复杂的大型应用。我们的ES6其实指的就是ECMAScript的一个版本。在互联网的各类教程中,我们往往会将ES5之后的内容统称为ES6。
ES是我们脚本语言的标准,也就是一套设计规则,是 JavaScript 语言的一次重大升级


1.变量声明

首先我们来讲讲新的变量声明方式,分别是const和let,简单来说:

const用来声明常量,let用来声明变量

// 例子 1-1

// bad
var foo = 'bar';

// good
let foo = 'bar';

// better
const foo = 'bar';

然后有几个细节,

A.使用var可以重复声明、赋值变量,使用letconst不能重复声明变量。

image.png

B.三种变量的使用优先级:

  • 不使用 var(容易污染全局作用域)
  • const 优先,let 次之

C.变量提升:

使用const声明的是常量,在后面出现的代码中不能再修改该常量的值(如果是引用类型,可以修改引用类型所指向的数据)

如果直接重新命名数组是会报错的 image.png

所以可以使用这种方法:

const arr = [0, 1, 2, 3];
const len = arr.push(4); // 数组末尾添加一个4,并返回数组添加后的长度
console.log(arr, len) // [0,1,2,3,4] 5

image.png


2. 模板字符串

  • 模板字符串是ES6中引入的一种新的字符串语法。它允许在字符串中插入变量或表达式,而不需要使用字符串拼接符号。模板字符串使用反引号``包围,并使用${}语法来插入变量或表达式。

需要拼接字符串的时候尽量改成使用模板字符串:

// 例子 2-1

// bad
const foo = 'this is a' + example;

// good
const foo = `this is a ${example}`;

//例子2-2
const a = 5;
const b = 10;
// 注意反引号对 `` 包裹
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

这里补充一点字符串相加

const str = '蓝山工作室';
const num = 888;
const boolean = true;
const nullVar = null;
const undefinedVar = undefined;

console.log(str + num); 
console.log(str + boolean);
console.log(str + nullVar);
console.log(str + undefinedVar);

输出结果如下图所示:


3.扩展操作符

  • 扩展操作符用于展开可迭代对象(如数组、字符串等),将其元素逐个展开,以便于在函数调用、数组字面量、对象字面量等地方使用。
  • 在使用扩展操作符时,你需要在要展开的可迭代对象前面加上三个点(...)。

1.展开数组

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

2.传递参数给数组

function sum(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];

const result = sum(...numbers); // 6

3.浅拷贝数组和对象

const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // [1, 2, 3]

const obj1 = {name: 'Alice', age: 20};
const obj2 = {...obj1}; // {name: 'Alice', age: 20}


4.解构赋值

ES6的解构赋值语法允许我们从数组或对象中提取值,并将它们赋给变量。

1.提取数组元素

假设你有一个数组,你想要将其中的元素赋值给不同的变量:

const numbers = [1, 2, 3, 4, 5];
const [first, second, third, fourth, fifth] = numbers;
console.log(first); // 输出: 1
console.log(second); // 输出: 2

2. 交换变量的值

在不使用临时变量的情况下交换两个变量的值:

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a); // 输出: 2
console.log(b); // 输出: 1

3. 从函数返回多个值

当你的函数需要返回多个值时,你可以使用解构赋值来接收这些值:

function getMinMax(numbers) {
  const min = Math.min(...numbers);
  const max = Math.max(...numbers);
  return [min, max];
}
​
const [min, max] = getMinMax([10, 20, 30, 40, 50]);
console.log(min); // 输出: 10
console.log(max); // 输出: 50

4. 忽略某些元素

如果你只对数组中的某些元素感兴趣,你可以忽略其他的元素:

const [, , thirdElement] = [1, 2, 3, 4, 5];
console.log(thirdElement); // 输出: 3

5. 使用剩余参数提取剩余元素

当你不确定数组的长度,但想要提取除前几个元素之外的所有元素时,可以使用剩余参数(rest parameter):

const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 输出: 1
console.log(second); // 输出: 2
console.log(rest); // 输出: [3, 4, 5]

6. 与对象解构结合

如果数组中包含对象,你可以同时解构数组和对象:

 const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 }
];

const [ { name: userName1 ,age:userAge1 }, { name: userName2,age: userAge2 } ] = users;
console.log(userName1,userAge1); // 输出: Alice 25
console.log( userName2,userAge2); // 输出: Bob 30

7. 函数参数解构

在函数参数中使用解构,使得代码更加简洁:

function printCoords([x, y]) {
  console.log(`X: ${x}, Y: ${y}`);
}
​
printCoords([3, 4]); // 输出: X: 3, Y: 4

通过这些例子,你可以看到解构赋值如何使数组操作更加直观和简洁。它不仅可以减少代码量,还可以提高代码的可读性。


5.内存空间

垃圾回收机制

JavaScript有垃圾自动回收机制,所以对于前端开发人员来说,内存分配并不是一个经常被提及的概念。

let a = 20;
alert(a + 100);
a = null;

分别对应如下三个过程:

  • 分配内存
  • 使用分配到的内存
  • 不需要时释放内存

这里要重点理解第三个过程,JavaScript的垃圾回收主要依赖“引用”的概念,当一块内存空间中的数据能够被访问时,垃圾回收器就认为“该数据能够被获得”,不能被获得的数据,就会被打上标记,并回收内存空间,即为标记清除算法

这个算法使垃圾回收器会找到所有可以获得与不能访问的数据。

如上面那个例子,我们将a设置为null时,最开始给其分配的20,就无法被访问到,很快会被自动回收。如果一个引用数据类型的值被改变,那么其指向的数据也无法被访问到,很快会被自动回收掉。

在局部作用域中,当函数执行完毕后,局部变量就会被清除,因此垃圾回收集器很容易做出判断并回收。但是在全局作用域中,变量什么时候被释放垃圾收集器很难判断,所以我们需要尽可能避免使用全局变量,尽量不使用var声明变量。如果使用了全局变量,则建议不使用它时,通过a=null来手动释放。

6.作用域

了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。

作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。

函数作用域

在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。

<script>
  // 声明 counter 函数
  function counter(x, y) {
    // 函数内部声明的变量
    const s = x + y
    console.log(s) // 18
  }
  // 设用 counter 函数
  counter(10, 8)
  // 访问变量 s
  console.log(s)// 报错
</script>

总结:

  1. 函数内部声明的变量,在函数外部无法被访问
  2. 函数的参数也是函数内部的局部变量
  3. 不同函数内部声明的变量无法互相访问
  4. 函数执行完毕后,函数内部的变量实际被清空了

块作用域

在 JavaScript 中使用 {} 包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。

<script>
  {
    // age 只能在该代码块中被访问
    let age = 18;
    console.log(age); // 正常
  }
  
  // 超出了 age 的作用域
  console.log(age) // 报错
  
  let flag = true;
  if(flag) {
    // str 只能在该代码块中被访问
    let str = 'hello world!'
    console.log(str); // 正常
  }
  
  // 超出了 age 的作用域
  console.log(str); // 报错
  
</script>

全局作用域

<script> 标签和 .js 文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。

<script>
  // 此处是全局
  
  function sayHi() {
    // 此处为局部
  }

  // 此处为全局
</script>

全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:

<script>
    // 全局变量 name
    const name = '小明'
  
  	// 函数作用域中访问全局
    function sayHi() {
      // 此处为局部
      console.log('你好' + name)
    }

    // 全局变量 flag 和 x
    const flag = true
    let x = 10
  
  	// 块作用域中访问全局
    if(flag) {
      let y = 5
      console.log(x + y) // x 是全局的
    }
</script>

总结:

  1. window 对象动态添加的属性默认也是全局的,不推荐!
  2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
  3. 尽可能少的声明全局变量,防止全局变量被污染

JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。

作用域链

在解释什么是作用域链前先来看一段代码:

<script>
  // 全局作用域
  let a = 1
  let b = 2
  // 局部作用域
  function f() {
    let c
    // 局部作用域
    function g() {
      let d = 'aaa'
    }
  }
</script>

函数内部允许创建新的函数,f 函数内部创建的新函数 g,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。

父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。

作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:

<script>
  // 全局作用域
  let a = 1
  let b = 2

  // 局部作用域
  function f() {
    let c
    // let a = 10;
    console.log(a) // 1 或 10
    console.log(d) // 报错
    
    // 局部作用域
    function g() {
      let d = 'yo'
      // let b = 20;
      console.log(b) // 2 或 20
    }
    
    // 调用 g 函数
    g()
  }

  console.log(c) // 报错
  console.log(d) // 报错
  
  f();
</script>

总结:

  1. 嵌套关系的作用域串联起来形成了作用域链
  2. 相同作用域链中按着从小到大的规则查找变量
  3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域

闭包

我们可以从一个例子入手:

function fun () {
  let num = 0
  function add () {
    num++
    console.log(num)
  }
  return add
}

let addFun = fun()
addFun() // 1
addFun() // 2
addFun() // 3
console.log(num) // ReferenceError: num is not defined

这个例子中,我们在fun函数里又定义了add函数,并将add函数返回给全局上下文进行使用。

正常情况下,函数执行完成,内部变量会被销毁(释放内存空间)。

通过打印结果可以看到,当fun函数执行完成之后,其内部的变量num本该跟着函数执行完成一起被清除,但fun函数返回的add函数赋值给addFun后,addFun内部可以却访问之前fun函数定义的num,但全局作用域无法访问。

这就形成了一个闭包。当一个函数a内部声明了另一个函数b,函数b中使用了函数a中声明的变量或者函数,并将函数b赋值给全局变量或者作为返回值传给全局变量,就形成了闭包。

特点
  1. 可以引用外部函数的作用域
  2. 可以创建私有作用域,且不会被回收内存。
  3. 容易导致内存泄露

7.箭头函数

ES6 允许使用“箭头”=>定义函数。

箭头函数相当于一个匿名函数,并且简化了函数定义。

括号内表达传入的参数,如果只有一个参数,括号可以省略,如果有多个参数或没有参数,则不能省略。

箭头后面为函数体,用大括号包裹。如果只含返回值,大括号可以省略。

let f = () => 5;
// 等同于
let f = function () { return 5 };

let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
  return num1 + num2;
};

//1。只有一个形参的时候,可以省略小括号
     const fn = x => {
       console.log(x)
     }
   fn(1) //打印1
   
 //2.只有一行代码的时候,我们可以省略大括号
     const fn = x => console.log(x)
     fn(1)
     
  // 3. 只有一行代码的时候,可以省略return
     const fn = x => x + x
     console.log(fn(1))
    


8.this

什么是this

想要知道一个东西是什么,我们首先可以直接将其打印出来看看:

console.log(this)

打印出来是window对象,即直接打印this指向全局对象。

如果我们在函数体内打印this

function fun () {
    console.log(this); // 还是window对象
}
fun()

打印出来仍是window全局对象。

在这是在非严格模式下,独立调用函数时,函数中的 this 指向全局对象(在浏览器环境中是 window 对象)。

在严格模式下,独立调用函数时,函数中的 this 为 undefined。如果要在严格模式下使函数中的 this 指向特定对象,可以使用方法调用(将函数作为对象的方法调用)、使用 call()apply() 或 bind() 方法来显式指定 this 的值。 由于window对象扮演着浏览器中的全局对象的角色,因此所有在全局作用域中声明的变量,函数都会变成window对象的属性和方法。

function fun() {
"use strict"; 
console.log(this); 
} fun(); // undefined

这里严格模式和非严格模式不做仔细讲解,链接 可自行了解


全局函数就是全局对象的方法。
window.fun()

现在我们调用对象中的方法打印this

const name = '蓝山'
const robot = {
  name: '蓝妹',
  sayName () {
    console.log(this)
    console.log(`我是${this.name}`)
  }
}
robot.sayName()

验证了我们的想法:this指向调用这个方法的对象,即谁调用它就指向谁。

箭头函数中的this

对于谁调用this,this就指向谁,仅适用于非箭头函数的函数。对于特殊的箭头函数,this指向需要单独来研究。

箭头函数外面指向谁,就指向谁。

实例如下,首先我们用普通函数的方式来实现如下代码:

let obj = {
  hp: 555,
  sayHp: function () {
    console.log(this.hp)
  }
}

obj.sayHp() // 555

如果我们使用箭头函数的方法来实现:

let obj = {
  hp: 555,
  sayHp: () => {
    console.log(this.hp)
  }
}

obj.sayHp() // undefined

会发现打印出来为undefined

call、apply、bind

callapplybind都是改变this指向的函数的方法。

call

  1. call可以立即调用并执行函数:

    1. function fun () {
          console.log('蓝山工作室')
      }
      fun.call() // 蓝山工作室
      
  2. call可以改变函数中this的指向:

      function fun () {
        console.log(this.name)
      }
    
      const cat = {
        name: 'aaa'
      }
    
      fun.call(cat) // aaa
    
  3. 考虑到传参的情况:

const dog = {
  name: '蓝妹',
  eat (food) {
    console.log(`我是${this.name},我要吃${food}`)
  }
}

const cat = {
  name: '邮宝'
}

dog.eat('骨头') // 我是蓝妹,我要吃骨头 
dog.eat.call(cat, '鱼') // 我是邮宝,我要吃鱼

apply

callapply的区别就在于传参的方式不同:

const dog = {
  name: '邮宝',
  eat (food, hobby) {
    console.log(`我是${this.name},我要吃${food},喜欢${hobby}`)
  }
}

const cat = {
  name: '蓝妹'
}

dog.eat.apply(cat, ['鱼', '睡觉']) // 我是蓝妹,我要吃鱼,喜欢睡觉

bind不会立即执行函数。

const dog = {
  name: '邮宝',
  eat (food, hobby) {
    console.log(`我是${this.name},我要吃${food},喜欢${hobby}`)
  }
}

const cat = {
  name: '蓝妹'
}

const fun = dog.eat.bind(cat, '鱼', '睡觉') // 什么都没打印 
fun() // 我是蓝妹,我要吃鱼,喜欢睡觉

bind会返回一个已经改变好this指向的函数,方便我们在后面自行调用执行。


9.数组操作方法,字符串操作方法

由于这部分内容包含的内容过多,建议有空余时间去自行了解,初学可以暂且只做了解 😂😂😂😂

数组方法

push()

push()可以将某些值加入到数组的最后一个位置,不限制添加数量,添加的内容使用逗号隔开即可,加入后数组长度会增加。

使用push()后会改变原本的数组内容。

let a = [1,2,3,4,5,6,7,8];
a.push(910);
console.log(a);  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

pop()

pop()会移除 (取出) 数组的最后一个元素。

使用pop()后会改变原本的数组内容。

let a = [1,2,3,4,5,6,7,8];
let b = a.pop();
console.log(a);  // [1, 2, 3, 4, 5, 6, 7]
console.log(b);  // 8

shift()、unshift()

shift()会移除 (取出) 数组的第一个元素。

使用shift()后会改变原本的数组内容。

let a = [1,2,3,4,5,6,7,8];
let b = a.shift();
console.log(a);  // [2, 3, 4, 5, 6, 7, 8]
console.log(b);  // 1

unshift()会将指定的元素添加到第一个位置。

使用unshift()后会改变原本的数组内容。

let a = [1,2,3,4,5,6,7,8];
a.unshift(100,200,300);
console.log(a);  // [100, 200, 300, 1, 2, 3, 4, 5, 6, 7, 8]

reverse()

reverse()会将数组反转。

使用reverse()后会改变原本的数组内容。

let a = [1,2,3,4,5,6,7,8];
a.reverse();
console.log(a); // [8, 7, 6, 5, 4, 3, 2, 1]

splice()

splice()可以移除或新增数组的元素,它包含了三个参数,第一个是要移除或要添加的序列号码 (必填),第二个是要移除的长度 ( 选填,若不填则第一个号码位置后方的所有元素都会被移除,若设定为 0 则不会有元素被移除 ),第三个是要添加的内容 ( 选填 )

使用splice()后会改变原本的数组内容。

let a = [1,2,3,4,5,6,7,8];
a.splice(5,1);
console.log(a);   // [1, 2, 3, 4, 5, 7, 8] ( 6 被移除了 )
let a = [1,2,3,4,5,6,7,8];
a.splice(5,1,100);
console.log(a);   // [1, 2, 3, 4, 5, 100, 7, 8] ( 6 被移除,100 加到第 5 个位置  )

let b = [1,2,3,4,5,6,7,8];
b.splice(5,3,100,200,300);
console.log(b);   // [1, 2, 3, 4, 5, 100, 200, 300] ( 6,7,8 被移除,100,200,300 加到第 5,6,7 个位置  )

let c = [1,2,3,4,5,6,7,8];
c.splice(5,0,100);
console.log(c);   // [1, 2, 3, 4, 5, 100, 6, 7, 8] ( 没有元素被移除,100 加到第 5 个位置 )

slice()

slice():返回从原数组中指定开始下标到结束下标之间的项组成的新数组。

slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。

只有一个参数的情况下, slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。

如果有两个参数,该方法返回起始和结束位置之间的项,但不包括结束位置的项。

var arr = [1,3,5,7,9,11];
var arrCopy = arr.slice(1);
var arrCopy2 = arr.slice(1,4);
var arrCopy3 = arr.slice(1,-2);//相当于arr.slice(1,4)
var arrCopy4 = arr.slice(-4,-1);//相当于arr.slice(2,5)
console.log(arr);   //[1, 3, 5, 7, 9, 11](原数组没变)
console.log(arrCopy);   //[3, 5, 7, 9, 11]
console.log(arrCopy2);   //[3, 5, 7]
console.log(arrCopy3);   //[3, 5, 7]
console.log(arrCopy4);   //[5, 7, 9]

concat()

concat()可以将两个数组合并在一起,如果是使用 ES6 语法也可以用扩展运算符...来代替。

let a = [1,2,3,4,5];
let b = [6,7,8,9];

let c = a.concat(b);
let d = [...a, ...b]; // 使用 ...

console.log(c); // [1,2,3,4,5,6,7,8,9]
console.log(d); // [1,2,3,4,5,6,7,8,9]

join()

join()可以将数组中所有元素,由指定的字符合并在一起变成字符串呈现,若没有指定字符默认会用「逗号」合并。

let a = [1,2,3,4,5,6,7,8];
console.log(a.join());      // 1,2,3,4,5,6,7,8
console.log(a.join(''));    // 12345678
console.log(a.join('@@'));  // 1@@2@@3@@4@@5@@6@@7@@8

sort()

sort()可以针对数组的元素进行排序,里头包含了一个排序用的判断函数,函数内必须包含两个参数,这两个参数分别代表数组里第 n 个和第 n+1 个元素,通过比较第 n 和第 n+1 个元素的大小来进行排序。

使用sort()后会改变原本的数组内容。

let a = [1,3,8,4,5,7,6,2];
a.sort((x,y) => y - x);
console.log(a);   // [8, 7, 6, 5, 4, 3, 2, 1]
a.sort((x,y) => x - y);
console.log(a);   // [1, 2, 3, 4, 5, 6, 7, 8]

filter()

filter()会将数组中的「每一个」元素带入指定的函数内做判断,如果元素符合判断条件则会返回,成为一个新的数组元素。

let a = [1,2,3,4,5,6,7,8];
console.log(a.filter(e => e > 3));    // [4, 5, 6, 7, 8]
console.log(a.filter(e => e%2 == 0)); // [2, 4, 6, 8]

indexOf()和 lastIndexOf()

接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。

indexOf(): 从数组的开头(位置 0)开始向后查找。

lastIndexOf: 从数组的末尾开始向前查找。

这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1。在比较第一个参数与数组中的每一项时,会使用全等操作符

var arr = [1,3,5,7,7,5,3,1];
console.log(arr.indexOf(5));   //2
console.log(arr.lastIndexOf(5));   //5
console.log(arr.indexOf(5,2));   //2
console.log(arr.lastIndexOf(5,4));   //2
console.log(arr.indexOf("5"));   //-1

字符串方法

1. 获取字符串长度

JavaScript中的字符串有一个length属性,该属性可以用来获取字符串的长度:

const str = 'hello';
str.length   // 输出结果:5

2. 获取字符串指定位置的值

charAt()和charCodeAt()方法都可以通过索引来获取指定位置的值:

  • charAt() 方法获取到的是指定位置的字符;
  • charCodeAt()方法获取的是指定位置字符的Unicode值。
(1)charAt()

charAt() 方法可以返回指定位置的字符。其语法如下:

string.charAt(index)

index表示字符在字符串中的索引值:

const str = 'hello';
str.charAt(1)  // 输出结果:e 
(2)charCodeAt()

charCodeAt():该方法会返回指定索引位置字符的 Unicode 值,返回值是 0 - 65535 之间的整数,表示给定索引处的 UTF-16 代码单元,如果指定位置没有字符,将返回 NaN

3. 检索字符串是否包含特定序列

这5个方法都可以用来检索一个字符串中是否包含特定的序列。其中前两个方法得到的指定元素的索引值,并且只会返回第一次匹配到的值的位置。后三个方法返回的是布尔值,表示是否匹配到指定的值。

(1)indexOf()

indexOf():查找某个字符,有则返回第一次匹配到的位置,否则返回-1,其语法如下:

string.indexOf(searchvalue,fromindex)

该方法有两个参数:

  • searchvalue:必需,规定需检索的字符串值;
  • fromindex:可选的整数参数,规定在字符串中开始检索的位置。它的合法取值是 0 到 string.length - 1。如省略该,则从字符串的首字符开始检索。
let str = "abcdefgabc";
console.log(str.indexOf("a"));   // 输出结果:0
console.log(str.indexOf("z"));   // 输出结果:-1
console.log(str.indexOf("c", 4)) // 输出结果:9
(2)lastIndexOf()

lastIndexOf():查找某个字符,有则返回最后一次匹配到的位置,否则返回-1

let str = "abcabc";
console.log(str.lastIndexOf("a"));  // 输出结果:3
console.log(str.lastIndexOf("z"));  // 输出结果:-1

该方法和indexOf()类似,只是查找的顺序不一样,indexOf()是正序查找,lastIndexOf()是逆序查找。

(3)includes()

includes():该方法用于判断字符串是否包含指定的子字符串。如果找到匹配的字符串则返回 true,否则返回 false。该方法的语法如下:

string.includes(searchvalue, start)

该方法有两个参数:

  • searchvalue:必需,要查找的字符串;
  • start:可选,设置从那个位置开始查找,默认为 0。

4. 连接多个字符串

concat() 方法用于连接两个或多个字符串。该方法不会改变原有字符串,会返回连接两个或多个字符串的新字符串。其语法如下:

string.concat(string1, string2, ..., )

其中参数 string1, string2, ..., stringX 是必须的,他们将被连接为一个字符串的一个或多个字符串对象。

let str = "abc";
console.log(str.concat("efg"));          //输出结果:"abcefg"
console.log(str.concat("efg","hijk")); //输出结果:"abcefghijk"

虽然concat()方法是专门用来拼接字符串的,但是在开发中使用最多的还是加操作符+,因为其更加简单。

5. 字符串分割成数组

split() 方法用于把一个字符串分割成字符串数组。该方法不会改变原始字符串。其语法如下:

string.split(separator,limit)

该方法有两个参数:

  • separator:必需。字符串或正则表达式,从该参数指定的地方分割 string。
  • limit:可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。
let str = "abcdef";
str.split("c");    // 输出结果:["ab", "def"]
str.split("", 4)   // 输出结果:['a', 'b', 'c', 'd'] 

其实在将字符串分割成数组时,可以同时拆分多个分割符,使用正则表达式即可实现:

const list = "apples,bananas;cherries"
const fruits = list.split(/[,;]/)
console.log(fruits);  // 输出结果:["apples", "bananas", "cherries"]

6. 截取字符串

substr()、substring()和 slice() 方法都可以用来截取字符串。

(1) slice()

slice() 方法用于提取字符串的某个部分,并以新的字符串返回被提取的部分。其语法如下:

string.slice(start,end)

该方法有两个参数:

  • start:必须。 要截取的片断的起始下标,第一个字符位置为 0。如果为负数,则从尾部开始截取。
  • end:可选。 要截取的片段结尾的下标。若未指定此参数,则要提取的子串包括 start 到原字符串结尾的字符串。如果该参数是负数,那么它规定的是从字符串的尾部开始算起的位置。

上面说了,如果start是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推:

let str = "abcdefg";
str.slice(1,6);   // 输出结果:"bcdef" 
str.slice(1);     // 输出结果:"bcdefg" 
str.slice();      // 输出结果:"abcdefg" 
str.slice(-2);    // 输出结果:"fg"
str.slice(6, 1);  // 输出结果:""

2) substr()

substr() 方法用于在字符串中抽取从开始下标开始的指定数目的字符。其语法如下:

string.substr(start,length)

该方法有两个参数:

  • start 必需。要抽取的子串的起始下标。必须是数值。如果是负数,那么该参数声明从字符串的尾部开始算起的位置。也就是说,-1 指字符串中最后一个字符,-2 指倒数第二个字符,以此类推。
  • length:可选。子串中的字符数。必须是数值。如果省略了该参数,那么返回从 stringObject 的开始位置到结尾的字串。
let str = "abcdefg";
str.substr(1,6); // 输出结果:"bcdefg" 
str.substr(1);   // 输出结果:"bcdefg" 相当于截取[1,str.length-1]
str.substr();    // 输出结果:"abcdefg" 相当于截取[0,str.length-1]
str.substr(-1);  // 输出结果:"g"

7. 字符串模式匹配

replace()、match()和search()方法可以用来匹配或者替换字符。

(1)replace()

replace():该方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串,它只替换第一个匹配子串。其语法如下:

string.replace(searchvalue, newvalue)
let str = "abcdef";
str.replace("c", "z") // 输出结果:abzdef

(2)replaceAll

在 JavaScript 中,replaceAll()方法用于将字符串中的所有匹配项替换为另一个字符串。

语法

str.replaceAll(searchValue, replaceValue)

  • searchValue:要被替换的字符串或正则表达式。如果是字符串,则会被严格匹配进行替换。如果是正则表达式,需要注意正则表达式的特殊字符和匹配规则。
  • replaceValue:用于替换的字符串。

该方法有两个参数:

  • searchvalue:必需。规定子字符串或要替换的模式的 RegExp 对象。如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象。
  • newvalue:必需。一个字符串值。规定了替换文本或生成替换文本的函数。

const str = "The quick brown fox jumps over the lazy dog. The fox is brown.";

// 将所有的 "fox" 替换为 "cat"
const newStr = str.replaceAll("fox", "cat");
console.log(newStr); 
// 输出:The quick brown cat jumps over the lazy dog. The cat is brown.

注意,在一些旧版本的浏览器中可能不支持 replaceAll()方法。在这种情况下,可以使用正则表达式结合 replace()方法来实现类似的功能:

const str = "The quick brown fox jumps over the lazy dog. The fox is brown.";
const newStr = str.replace(/fox/g, "cat"); 
console.log(newStr); 
// 输出:The quick brown cat jumps over the lazy dog. The cat is brown.

8. 移除字符串收尾空白符

trim()、trimStart()和trimEnd()这三个方法可以用于移除字符串首尾的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。

trim()

trim() 方法用于移除字符串首尾空白符,该方法不会改变原始字符串:

let str = "  abcdef  "
str.trim()    // 输出结果:"abcdef"

注意,该方法不适用于null、undefined、Number类型。


10. promise

注意此时应该大部分同学没有学过异步操作,为了保证完整性,只是在此处提及,可以跳过。(Async也在后面讲了)😂😂😂😂

  • Promise是一个表示异步操作最终完成或失败的对象。

  • 它可以有三种状态:

    • pending(进行中)
    • resolved(已完成)
    • rejected(已失败)
  • 特点

    • 对象的状态不受外界影响
    • 一旦状态改变就不会再变,任何时候都可得到这个结果
  • 声明:new Promise((resolve, reject) => {})

  • 出参

    • resolve:将状态从未完成变为成功,在异步操作成功时调用,并将异步操作的结果作为参数传递出去

    • reject:将状态从未完成变为失败,在异步操作失败时调用,并将异步操作的错误作为参数传递出去

例子

const myPromise = new Promise((resolve, reject) => {
  // 通过setTimeout模拟了一个耗时1秒的异步操作
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      // 操作成功,调用resolve函数
      resolve(randomNumber);
    } else {
      // 操作失败,调用reject函数
      reject(new Error('操作失败'));
    }
  }, 1000);
});

// 使用then方法处理Promise的结果
myPromise.then((result) => {
  console.log('操作成功:', result);
}).catch((error) => {
  console.log('操作失败:', error);
});

  • then() :用于处理异步操作成功的情况
  • catch() :用于处理异步操作失败的情况。

11.Module

  • 命令

    • export:规定模块对外接口

      • 默认导出export default Person(导入时可指定模块任意名称,无需知晓内部真实名称)
      • 单独导出export const name = "Bruce"
      • 按需导出export { age, name, sex }(推荐)
      • 改名导出export { name as newName }
    • import:导入模块内部功能

      • 默认导入import Person from "person"
      • 整体导入import * as Person from "person"
      • 按需导入import { age, name, sex } from "person"
      • 改名导入import { name as newName } from "person"
      • 自执导入import "person"
      • 复合导入import Person, { name } from "person"
    • 复合模式export命令import命令结合在一起写成一行,变量实质没有被导入当前模块,相当于对外转发接口,导致当前模块无法直接使用其导入变量

      • 默认导入导出export { default } from "person"
      • 整体导入导出export * from "person"
      • 按需导入导出export { age, name, sex } from "person"
      • 改名导入导出export { name as newName } from "person"
      • 具名改默认导入导出export { name as default } from "person"
      • 默认改具名导入导出export { default as name } from "person"
  • 继承:默认导出改名导出结合使用可使模块具备继承性

  • 设计思想:尽量地静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量

  • 严格模式:ES6模块自动采用严格模式(不管模块头部是否添加use strict)


12.Iterator -迭代器

  • 定义:为各种不同的数据结构提供统一的访问机制
  • 原理:创建一个指针指向首个成员,按照次序使用next()指向下一个成员,直接到结束位置(数据结构只要部署Iterator接口就可完成遍历操作)
    • 迭代器对象方法:

    • next() :下一步操作,返回{ value,done }(必须部署)

    • return()for-of提前退出调用,返回{ done: true }

    • throw() :不使用,配合Generator函数使用

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
​
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

通俗易懂的说就是,迭代器是一种对象,它定义了一种用于遍历集合(如数组、对象等)的方法。迭代器使得可以按顺序访问集合中的元素,而无需了解集合的内部结构。

  • forEach 是数组的专用方法,只能用于数组。
  • for...in 可以用于遍历数组和对象的属性,但不能保证属性的遍历顺序。
  • forEach 通常用于数组元素的遍历,而 for...in 更适用于遍历对象的属性。

forEach

forEach 是数组的高阶函数,用于遍历数组中的每个元素。它接受一个回调函数作为参数,该回调函数会在数组的每个元素上执行。

const numbers = [1, 2, 3, 4, 5];
​
numbers.forEach(function(number, index) {
  console.log(`Index: ${index}, Value: ${number}`);
});
​
//会输出数组的每个元素的索引和值

for...in

for...in 循环用于遍历一个对象的所有可枚举属性。它可以用来遍历数组和对象。

const person = { 
   name: 'Alice',
   age: 30,
   city: 'New York' 
   };
for (let key in person) { 
console.log(`${key}: ${person[key]}`); 
}
name: Alice age: 30 city: New York

for...of 循环

  • for...of循环用于遍历可迭代对象(例如数组、字符串、Set、Map等),它会迭代对象中的每个元素并执行指定的代码块。

遍历数组:

let arr = [1, 2, 3, 4];

for(let element of arr) {
  console.log(element);
}
// 输出:
// 1
// 2
// 3
// 4

遍历字符串

let str = "Hello";
​
for(let char of str) {
  console.log(char);
}
// 输出:
// H
// e
// l
// l
// o
ES6的内容其实还有很多,我们暂且就重点学习这些知识,这节课就到此结束咯,加油!😊😊😊😊