Let、Const、Var
1. 区别
| 区别 | var | let | const |
|---|---|---|---|
| 是否有块级作用域 | × | ✔️ | ✔️ |
| 是否存在变量提升 | ✔️ | × | × |
| 是否添加全局属性 | ✔️ | × | × |
| 能否重复声明变量 | ✔️ | × | × |
| 是否存在暂时性死区 | × | ✔️ | ✔️ |
| 是否必须设置初始值 | × | × | ✔️ |
| 能否改变指针指向 | ✔️ | ✔️ | × |
2. 带来的问题
- 使用var声明变量的问题
- 允许重复的变量声明会导致数据被覆盖
- 变量提升:怪异的数据访问、闭包问题
- 全局变量挂载到全局对象:全局对象成员污染问题
- 使用let声明变量
- 会有块级作用域:内层变量可能覆盖外层变量;用来计数的循环变量泄露为全局变量。
- 声明变量的问题
- let声明的变量不会挂载到全局对象
- let声明的变量,不允许当前作用域范围内重复声明
- 在块级作用域中用let定义的变量,在作用域外不能访问
- 使用let不会有变量提升,因此,不能在定义let变量之前使用它
- 底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access 'a' before initialization”。
- 当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
- 在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)
- 在循环中使用let声明的循环变量,在循环结束后会销毁
- 使用const声明常量
- const和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:
- 根据经验,开发中的很多变量,都是不会更改,也不应该更改的。后续的很多框架或者是第三方JS库,都要求数据不可变,使用常量可以一定程度上保证这一点。
- 注意的细节:
- 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
- 常量的命名
- 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割。
- 普通的常量:使用和之前一样的命名即可。
- 在for循环中,循环变量不可以使用常量,不然无法循环变化。
异步、promise
每个promise对象(异步任务)都有两个阶段,三个状态
- 两个阶段:unsettled(未决) settled(已决)
- 三个状态:pending(挂起状态) fulfilled(完成状态(通过resolve方法得到)) rejected(失败状态(通过reject方法得到))
- 针对 resolved 的后续处理称为thenable
- 针对rejected 的后续处理称为 catchable
类
1. 类:构造函数的语法糖
- 传统的构造函数的问题
- 属性和原型方法定义分离,降低了可读性
- 原型成员可以被枚举
- 默认情况下,构造函数仍然可以被当作普通函数使用
- 类的特点
- 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
- 中的所有代码均在严格模式下执行
- 类的所有方法都是不可枚举的
- 类的所有方法都无法被当作构造函数使用
- 类的构造器必须使用 new 来调用
- 新旧对比:
// function Animal(type,name,age,sex){ // this.type = type, // this.name = name, // this.age = age, // this.sex = sex // } // Animal.prototype.print = function(){ // console.log(`种类:${this.type}`); // console.log(`名字:${this.name}`); // console.log(`年龄:${this.age}`); // console.log(`性别:${this.sex}`); // } // const a = new Animal('狗','旺财',3,'男'); // a.print(); // for(const prop in a){ // console.log(prop); // } // class Animal{ // constructor(type,name,age,sex){ // this.type = type, // this.name = name, // this.age = age, // this.sex = sex // } // print(){ // console.log(`种类:${this.type}`); // console.log(`名字:${this.name}`); // console.log(`年龄:${this.age}`); // console.log(`性别:${this.sex}`); // } // } // const a = new Animal('狗','旺财',3,'男'); // a.print(); // for(const prop in a){ // console.log(prop); // } - 类的其他书写方式
- 匿名类
const A = class { constructor(){} say(){} } const a = new A();
- 匿名类
2. 类的继承
- 概念: 如果B继承自A,A是B的父类,则B会自动拥有A中所有实例,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数;如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器。
- 新的关键字:
- extends: 继承,用于类的定义
- super: 直接当作函数调用,表示父类构造函数;如果当作对象使用,则表示父类的原型
// class Animal { // constructor(type, name, age, sex) { // this.type = type; // this.name = name; // this.age = age; // this.sex = sex; // } // print() { // console.log(`【种类】:${this.type}`); // console.log(`【名字】:${this.name}`); // console.log(`【年龄】:${this.age}`); // console.log(`【性别】:${this.sex}`); // } // jiao(){ // throw new Error("动物怎么叫的?"); // } // } // class Dog extends Animal{ // constructor(name,age,sex){ // super('犬类',name,age,sex); // this.loves = '吃骨头'; // } // print(){ // // 调用父类的print // super.print(); // //自己的方法 // console.log(`爱好:${this.loves}`); // } // jiao(){ // console.log('旺旺!'); // } // } // const dog = new Dog('旺财',3,'公'); // dog.print(); // dog.jiao(); // console.log(dog);
3. 继承(六种继承方式)
- 原型链继承: 就是让子类的原型等于父类的实例对象,这样继承的子类的实例共享了父类实例上的方法,当一个子类实例修改了父类实例的属性,其他子类实例的属性就会发生变化。但是缺点是:过多的继承了没用的属性,构造实例的时候不能向父类的构造函数传递参数。
- 借用构造函数继承: 在创建实例的时候可以向父类构造函数传递参数,可以实现多继承,缺点:子类实例没有继承到父类原型上的属性和方法。
- 组合式继承: 就是原型链继承和构造函数继承的结合,它的特点是,可以向父类的构造函数传递参数,也可以继承父类原型上的属性和方法,但是每次创建函数的时候,都会调用两次父类的构造函数,造成性能上和内存的损耗。
- 原型式继承: 类似于复制一个对象,用函数包装起来,这样的继承会使所有原型都会继承原型上的属性,但是创建出来的新实例的属性都是要在后面添加的。
- 寄生式继承: 寄生式继承相当于给原型式继承又套了一层函数传递参数,可以为原型式继承创建的对象添加属性。
- 寄生组合式继承: 解决了组合式继承的缺点:调用两次父类的构造函数。
ESmodule common js
1. 目的:
- 解决全局变量污染
- 解决依赖混乱
- 更容易细分文件
2. 导入导出方式
- commonJs: 是一个社区规范,出现的时间较早,目前仅node环境支持,
- 导入: 函数require("要导入的模块路径"),可以省略.js,必须以./或../开头
- 有缓存: 一个模块在被导入时会运行一次,node会缓存导出结果,重新导入时不会再运行该模块
- 导出: 在模块中给module.exports赋值,赋什么类型则导出什么类型
- Es Module: ES6发布的官方模块化标准,目前浏览器和新版本node环境均支持
- 导入: 使用关键字import,同时可以更改导入的变量的名称。
- 导出: 使用关键字export,分为具名导出(普通导出),可以导出多个;默认导出,只能导出一个;
- 静态导入的代码绑定的符号是常量,不可更改
// //仅运行一次该模块,不导入任何内容 // import "模块路径" // // 常用,导入属性 a、b,放到变量a、b中。a->a, b->b // import { a, b } from "模块路径" // // 常用,导入属性 default,放入变量c中。default->c // import c from "模块 路径" // // 常用,default->c,a->a, b->b // import c, { a, b } from "模块路径" // // 常用,将模块对象放入到变量obj中 // import * as obj from "模块路径" // // 导入属性a、b,放到变量temp1、temp2 中 // import {a as temp1, b as temp2} from "模块路径" // // 导入属性default,放入变量a中,default是关键字,不能作为变量名,必须定义别名 // import {default as a} from "模块路径" // //导入属性default、b,放入变量a、b中 // import {default as a, b} from "模块路径" // // 以上均为静态导入 // import("模块路径") // 动态导入,返回一个Promise,完成时的数据为模块对象 //导出:具名导出(普通导出),可以导出多个;默认导出,只能导出一个;一个模块可以同时存在两种导出方式,最终会合并为一个「对象」导出 // export const a = 1; // 具名,常用 // export function b() {} // 具名,常用 // export const c = () => {} // 具名,常用 // const d = 2; // export { d } // 具名 // const k = 10 // export { k as temp } // 具名 // export default 3 // 默认,常用 // export default function() {} // 默认,常用 // const e = 4; // export { e as default } // 默认 // const f = 4, g = 5, h = 6 // export { f, g, h as default} // 基本 + 默认 // 以上代码将导出下面的对象 /* { a: 1, b: fn, c: fn, d: 2, temp: 10, f: 4, g: 5, default: 6 } */ // export {xx} // import {xx} from './' // export default xx // import xx from './'
3. commonJS和ES Module的区别
- 导入时文件后缀:
- ommonJS在导入时可以省略文件后缀.js,而ES Module不可以省略
- 依赖类型
- commonJS为"动态依赖",在运行到代码块的时候才导入其他东西,导出可以写在其他代码块中
- ES Module为"静态依赖",在代码运行之前分析出依赖关系,导出时必须为顶级导出,不能放在代码块中
- ES Module中出现import('模块路径')函数时才可以动态依赖
- 运行环境
- commonJs可以直接在node上面运行,而Es Module则要么加上type="module"后在浏览器运行,要么在node运行时需要在package.json里面加上type="module"
ES6新增API
1. Object.is 用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点
- NaN和NaN相等 +0和-0不相等
- console.log(Object.is(NaN,NaN));
2. object.assign 用于混合对象,进行覆盖重整
-
// const obj1 = { // a:1, // b:2, // c:3 // } // const obj2 = { // c:0, // d:4, // } // const obj3 = Object.assign({},obj1,obj2); // console.log(obj1); // console.log(obj2); // console.log(obj3); // const obj = Object.assign(obj1,obj2); // console.log(obj);
4. Object.getOwnPropertyNames 的枚举顺序,ES6规定了该方法返回的数组的排序方式如下:
- 先排数字,并按照升序排序
- 再排其他,按照书写顺序排序
- 返回排好序的属性名,不返回属性值 // const obj = { // d:1, // b:2, // a:3, // 0:6, // 5:2, // 4:1 // } // const props = Object.getOwnPropertyNames(obj); // console.log(props);
5. Object.setPrototypeOf 该函数用于设置某个对象的隐式原型
- 比如: Object.setPrototypeOf(obj1, obj2), 相当于: obj1._proto = obj2
// const obj1 = { // a:1 // } // const obj2 = { // b:2 // } // Object.setPrototypeOf(obj1,obj2); // console.log(obj1); //比如: // function Animal(type, name, age, sex) { // this.type = type; // this.name = name; // this.age = age; // this.sex = sex; // } // Animal.prototype.print = function () { // console.log(`【种类】:${this.type}`); // console.log(`【名字】:${this.name}`); // console.log(`【年龄】:${this.age}`); // console.log(`【性别】:${this.sex}`); // } // function Dog(name, age, sex) { // //借用父类的构造函数 // Animal.call(this, "犬类", name, age, sex); // } // Object.setPrototypeOf(Dog.prototype, Animal.prototype); // const d = new Dog("旺财", 3, "公"); // d.print(); // console.log(d);