前端学习笔记003-1:JavaScript 语法补充

发布于:2022-12-07 ⋅ 阅读:(634) ⋅ 点赞:(0)

之前我不是写过一篇 JS 的博客吗,感觉虽然写了很多字,但是那点知识在面对前端框架的时候,还是不够用(T_T)所以,本文对 JavaScript 进行一些语法补充,让我们能使用 JS 写出更多的东西,同时也是为后面的前端框架学习打下良好的基础~

(Node.js 在路上了~ 马上就来~)

目录

1. ES6 语法补充

1.1 箭头函数

1.2 解构赋值

1.3 模板字符串

1.4 字典(对象)初始化简写

1.5 字典(对象)传播操作符

1.6 数组 map 与 reduce

2. JavaScript 面向对象高级

2.1 super() 方法

2.2 原型与原型链

2.3 this 的指向

3. 其他知识点补充

3.1 模块化编程

3.2 DOM 的其他属性

3.3 setTimeout 与 setInterval

4. 小结


1. ES6 语法补充

之前我们使用的 JS 版本,确实是 ES6,but,用了,但没完全用到~ 解构赋值,箭头函数这些东西才是 ES6 里主要的语法补充~ 所以下文我们开始学习~

1.1 箭头函数

有些时候,我们想写一个函数,但是由于代码量比较少,又不想写 function,怎么办呢?ES6 里增加的语法箭头函数可以帮你~ 箭头函数也属于函数,可以直接赋值给变量,或是当作参数传递给普通函数(普通的也可以)~

// 箭头函数语法:
// (函数的形参列表) => { 函数体 }
let func = () => {
    console.log("Hello!");
}

这个变量(实际上是函数)既可以当作函数调用,又可以当作变量进行一系列的变量操作。此外,箭头函数非常灵活,有各种简写方式,下面一一列出来:

// 1. 当你的箭头函数只有一个参数时可以写成这样的形式
let func1 = arg => { 
    console.log(arg);
}
// 当然如果你的箭头函数没有参数时还是要加括号的

// 2. 当你的箭头函数的函数体只有一行代码时可以省略花括号,也不用换行~
let func2 = () => console.log("Hello World!");
// 请注意语句末加一个分号就行~

// 3. 以上两种 BUFF 可以叠加~
let func3 = arg => console.log(arg);

1.2 解构赋值

比如有一个数组,你想把数组里的每一项都赋给一个变量,但是又不想一个一个去敲太麻烦。这时候我们就可以使用解构赋值,它可以帮助你快速地把数组里的项遍历,并赋给变量~

let arr = [1,"小明",3];
let [id,name,age] = arr;

上面这个示例我们把数组 arr 里的三个值分别解构赋值给了 id name age 三个变量。我们可以打印这三个变量看看赋值是否成功。除此之外,解构赋值还可以解构字典(即我们常说的对象),遍历的值是字典中每一个键值对里的值。

1.3 模板字符串

我们有时候经常会遇见这种情况:想打印一个字符串,字符串里有很多 JS 变量,但是我们又不想一个一个使用 + 号连接,因为太麻烦了(T_T)这时候就可以使用模板字符串了,它允许你在字符串里使用 $ 符号(没错,就是 jQuery 里的那个美元符号)后面接一个表达式(表达式是啥我后面会讲)包在大括号里,它适用于字符串里要嵌入 JS 变量的情景。

let age = 3;
let name = "小明";
let str = `我叫${name},我今年 ${age} 岁了`;
// 上面就是模板字符串的应用,语法大家应该都看懂了把~
// 注意模板字符串的开头结尾不是双引号 ",而是这个符号 `

1.4 字典(对象)初始化简写

之前说过,字典(我们这里把对象称作字典),其实就是包含一个个键值对的数组(键值对就是我们平时说的 属性: 值 这种形式),它定义的时候,其实可以简写~ 下面来个例子~

// 正常的字典
let dict1 = {
    id: 1, // 逗号不能丢
    name: "小明",
    age: 3
}

// 简化定义字典
let id = 1;
let name = "小明";
let age = 3;
let dict2 = {
    id,
    name,
    age
}

// 看着好像没有简化,实际上这些变量开发中肯定都事先定义好了

效果非常 nice:

c2a9433e75c645ba926a43e5bb64eefd.png

1.5 字典(对象)传播操作符

对象传播操作符,其实就是把一个对象里的东西转移到另一个对象,话不多说我们上例子:

let newDict = {
    one:1,
    two:2,
    three:3,
    four:4,
    five:5,
    six:6,
    seven:7,
    eight:8
};

// 刚学的解构赋值就用上了~
let {one,two,...numberDict} = newDict;
// 上面这段代码的意思是把 newDict 的第一项的值赋给 one,第二项的值赋给 two,剩余的键值对全部赋给 numberDict 字典~

可以打印一下 numberDict:

d4bfa4f5376148d0b82042466bc43bd4.png

1.6 数组 map 与 reduce

数组的 map 方法是数组方法里最常用的一个。简单来说 map 方法就是一个微型的循环系统,传入的参数是一个箭头函数,方法会循环执行这个函数,然后保存函数的返回值。最后,map 方法创建一个新数组,把每一次函数的返回值全部塞进去,返回给用户。箭头函数的参数有指定:element 与 index。element 在函数中代表这次循环遍历到的数组项,index 代表该项的索引。其中 index 可要可不要。话不多说我们来例子:

let arr = [1,2,3,4,5,6,7,8];
let newArr = arr.map((element,index)=>{
    console.log("数组中的第" + index + "项是" + element);
    return element*2;
});
console.log(newArr);
// 它与下面的代码是等价的
let array = [1,2,3,4,5,6,7,8];
let newArray = [];
for (index in array){
    console.log("数组中的第" + index + "项是" + array[index]);
    newArray.push(array[index]*2);
}
console.log(newArray);
// 很明显使用 Map 方法简洁了许多~

注意观察控制台输出,它会帮助你更好的理解;

4e37a939bbfc4fe18f71607465ace518.png

 此外还有一个 reduce 方法,但是我们一般不常用。但是它是一个非常强大的方法,所以这里给出 JS 官方文档:MDN。上面写着是 Array.prototype.reduce,我们实际使用只需要数组名.reduce 就可以,关于 prototype 是什么我们很快会看到~

2. JavaScript 面向对象高级

接下来我们好好聊一聊 JS 的面向对象。之前对 JS 里的面向对象只是有一个初步的了解,现在我们开始深入学习~

2.1 super() 方法

这个方法相对来说还是比较简单的。super 方法的作用就是快速调用父类的构造器。如果子类继承了父类,子类自己又有写构造器的话,子类的构造器里面一定要写 super,而且要放在第一行去写,要不然报错。super 的参数是父类构造器的参数,执行完之后就会带着这些参数调用父类的构造器。所以你执行了 super 之后就不需要再初始化一遍父类已经有的属性了。下面给一个例子:

class Person{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
}

class Student extends Person{
    constructor(name,age,score){
        super(name,age);
        this.score = score;
    }
}

我们可以 new 一个 Student 的实例,打印出来看看:

let stu1 = new Student("小明",3,100);
console.log(stu1);

观察该实例的属性,我们可以发现 super 方法已经被成功调用~

95bdcf7ad93f4c79996d8974a0e0f43e.png

2.2 原型与原型链

原型与原型链是 JS 里的一个重难点。原型是啥呢?大家都知道,面向对象编程(OOP)里有继承这个很重要的概念。原型其实就是继承关系里的父与子中的父。但是它有一个不同的地方,比方说 B 类继承了 A 类,C 类继承了 B 类,那么 A 类与 B 类都是 C 类的原型。如果一个类没有继承其他类,那他也会继承 Object。Object 是 JS 里内置的一个类,它是所有类的原型。原型之间的关系通过原型链来构建。

那原型链又是啥呢?我们以上面那个 student 和 person 举例子。根据上面的知识,我们可以得出 person 是 student 的原型。Object 是 person 的原型。它们之间形成了一个链式的结构,这就是原型链。

27148cd6ff1b4e41b853e577dd5ff5ed.png

原型链(绘图:Gitmind)

其实从浏览器里我们也可以发现原型链的踪迹。我们试着展开一下刚刚打印的 Student 类的实例,你可以清楚地看见 Person 类与 Object 类。

5c0bbc160b044160a5bd3392b67b6664.png

 那怎么获取一个类的原型呢?总不能每次都打开浏览器查吧?其实刚刚那张图里也说了,[[Prototype]] 指的是一个类的原型,那我们怎么在 JS 里获取呢?很简单,使用类的 prototype 属性。如果你只有这个类的实例,那么可以通过实例的 __proto__ 属性来获取原型。

console.log(Student.prototype);
console.log(stu1.__proto__);

这里还需要注意一个,一个类的原型链里的所有原型指的都是原型对象,而不是普通的类。所以不能拿这个类来 new 一个新实例。这些原型对象全部都是这个类的子属性,即全部属于这个类。 

那有同学又说了:我刚刚辛辛苦苦学了一个原型,它代表父类这个我理解。但是原型链又有啥用?下面就要说说 JS 里的属性查找机制了。比如我们定义了几个类,类的源代码如下:

class A {
    printA(){
        console.log("这是 A 的函数 printA");
    }
}

class B extends A {
    printB(){
        console.log("这是 B 的函数 printB");
    }
}

class C extends B{
    printC(){
        console.log("这是 C 的函数 printC");
    }
    
    printA(){
        console.log("这是 C 的函数 printA");
    }
}

如上我们看到我们分别定义了三个类 A,B,C,它们之间存在着继承关系。除此之外它们还各自拥有一个方法 printA printB printC,其中 C 类又定义了一个与 A 类的函数重名的函数 printA。下面我们新建一个 C 类的实例,然后调用 printA 函数,看看效果:

b66141f2882d4bd498adaf8ec49ab876.png

很明显,它调用的是 C 类重新定义的函数 printA。那它为什么会调用 C 类重新定义的呢?这就要涉及到原型链了。JS 调用该函数时会先查找 C 类的属性,它发现查找不到,就继续往下(补充:一个类里的函数实际上被放在了它的原型对象上。这里的原型对象指的是类里的那个,并不是代表它真正的原型类。

edfa8684b0764212bda8d1b7b23694a4.png

然后找到它的原型 B,在里面发现了 printA 函数,随后立即调用。JS 由于已经发现了一个 printA 函数了,所以就不往下在查了。如果你是定义了一个 B 类的实例,那么调用的是 A 类的 printA。这就是 JavaScript 查找属性的机制,它实际上是顺着原型链往下查找的。那这个东西又有啥用呢?以后你写代码时对一个函数的执行结果感到困惑时就可以想想你调用的函数是否真的是你需要调用的函数。

206a1990e7424d73a6a9249a927d8cab.png

2.3 this 的指向

之前我们已经说过了在一个类中,this 指向这个类的实例。那为什么我们还要单独开一个小节来讲这个 this 的指向呢?因为后面在学 React 的时候,有一个非常坑的问题关乎这个 this 的指向。问题的根源就在于:this 它在一些场合不指向这个类的实例呀!所以下面我们就把这个问题细细地抠出来讲。

关于这个 this 的指向,最官方的表达是:在一个类中,被实例调用的函数中的 this 指向这个类的实例。这里多了一个什么?被实例调用的函数。那你可能就觉得:类里的函数肯定要被实例调用呀,难道你没有得到实例你就调用啊?还真的有那么一种情况,函数不是被实例调用的,即这个函数作为回调函数的时候。回调函数简单来说,就是把一个函数赋给变量或是当作参数传递,那么这个函数就是回调函数。回到前面的箭头函数的部分,其实箭头函数的创建初衷就是让回调函数的写法更加简洁的。下面我们演示一个例子:

class Student{
    printInfo(){
        console.log(this);
    }
}

let stu1 = new Student();
let x = stu1.printInfo;
x();

这里我们把 Student 类里的 printInfo 函数作为了回调函数赋给 x。然后调用 x。你会发现它打印出了 undefined(未定义)(如果打印出 Window 的请检查一下你的严格检查模式有没有开)。由于只有实例调用 printInfo 函数时 JS 才会打印实例,而这里的 printInfo 函数是作为回调函数调用的,自然 this 不会指向实例了。那 this 不指向实例,指向什么呢?自然只有 undefined 了。

那怎么避免这个错误呢?有两种解决办法,前一种是使用 bind 函数修复的方式,由于太麻烦我们不讲。后一种呢,就是我们熟知的箭头函数了。我们可以在类里定义一个属性,属性的值是一个箭头函数。那箭头函数怎么就正常了呢?对不起,JS 底层就是这样的~ 我们可以把上面的 printInfo 函数更改成箭头函数,然后再次实验看看是否正常。

class Student{
    printInfo = () => {
        console.log(this);
    }
}

再次把 printInfo 作为回调函数,你会发现,打印正常~

00ea099fc709451b9346eb948b31f85f.png

这里给个小 tip:通过属性 + 箭头函数定义的函数放在了这个类本身上,不是放在它的原型上的~

前面说了那么多,有的同学就要问了:我们为啥有事没事去把函数作为回调函数使用呢?这里可以给你给出一个回答:后面学 React 要用到~(那如果你不学 React 去学 Vue 那我也没法回答了……)

3. 其他知识点补充

3.1 模块化编程

之前我们不是学过接口与 import 吗,发现讲的还是太简单了(T_T)所以这一小节对它进行一个补充~

首先说说什么是模块化编程。模块化,其实就是把一个 JS 文件拆分成许多小的 JS 模块,这个很好理解。我们之前学的接口与 import 就是为了实现模块化。

0196eac5893842e5b3c00e17fe0ebb8f.png

模块化(绘图:Gitmind)

如果我们在一个模块里只需要将一个类或函数作为接口供外部使用,其实根本不需要费尽千辛万苦写一个字典,直接在 class 或 function 关键字之前加上 export default 即可。下面是一个例子:

export default class Student {}

上面的代码等价于如下形式:

class Student {}

export default {
    Student
}
// 代码看不懂的同学,刚刚学的对象初始化简写是不是忘了~

我们导入的时候,如果只需要导入一个模块里面的一个类或一个函数,那么可以使用花括号来包裹需要引入的类或函数:

// 假设已经准备好了一个 module.js,里面有一个 Component 类被当作了接口
import {Component} from "./module.js";

通过这种方式引入的类或函数可以直接通过函数名/类名使用~

可不要把上面那个花括号理解为解构赋值哈~

3.2 DOM 的其他属性

DOM 除了自带的属性与 innerHTML 属性之外,还有一些其他的属性。这些属性是 JS 为我们封装好的。由于这里的属性太多太繁杂,所以我们就直接给出官方文档链接~

(悄咪咪地水了一个小节~)

3.3 setTimeout 与 setInterval

这两个方法一个是设置等待时间的,一个是设置定时器的,它们都内置在 JS 里。先讲 setTimeout。这个函数有两个参数,第二个参数是等待时间的毫秒数,第一个参数是等待这些时间后要做的事情,参数通常为一个箭头函数,下面举个例子:

setTimeout(() => {
    console.log("Hello!");
}, 1000); // 这里的 1000 指的是 1000 毫秒,即 1 秒

执行上面的代码,你应该会看到这个函数先返回了一个值,值不固定,然后 1 秒之后它打印了 Hello~

然后是 setInterval。这个函数也有两个参数。第二个参数是每隔几毫秒运行一次代码,第一个参数是运行的代码,通常为一个箭头函数。

setInterval(() => {
    console.log("Hello");
}, 1000);

执行此代码,你会发现每隔 1 秒控制台会打印 hello 一次,想要停止定时器就使用 JS 内置的 clearInterval   方法,由于控制台的特殊原因所以没法停止,但实际敲代码时是可以停止的。这个函数不需要参数~

4. 小结

到这我们 JS 语法的补充就结束了,这个小节主要是补充内容所以比较短,但是它让我们一整个的 JS 知识链更加的完整,更好地应对后面的前端框架的学习。Node.js 基本已经完工了,很快就可以见到~

本文含有隐藏内容,请 开通VIP 后查看