在前端宝典二十一中,我们已经一起探讨了Promise\ async\ await
,并手写了Promise相关的方法all、race、allSettled、any
等。
这一讲我们一起来看ES6其他重点的内容
一、let、const
let和const的出现,首先是基于var存在的问题,来解决问题的
var存在的问题:
- 变量提升
- 无法形成词法作用域
- 可以随意篡改变量值、重复声明
1、let与const
- let是声明块级作用域的局部变量,只在代码块内有效
- const声明的是只读常量,
常量必须在声明时初始化
这样声明的好处是安全、简洁、规范
2、解决的问题
1. 循环中使用let声明变量避免问题
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
})
}
上面的代码如果用var声明循环的变量,就会打印出5个5
2. const常量防止被篡改
const PI =3.1415926;
const URL='https://editor.csdn.net/md?articleId=141468095';
3. const声明对象属性避免被修改
const uer = {
name: 'frank',
age: 18,
gender: 'Male'
}
user.like='sports'; // { name: 'frank', age: 18, gender: 'Male', like: 'sports' }
Object.freeze(user);
user.like='music'
console.log(user); // 报错
3、底层原理
let 的底层实现过程
-
编译阶段
在代码编译阶段,编译器会扫描整个函数体(或全局作用域),查找所有使用 let 定义的变量,为这些变量生成一个初始值为 undefined 的词法环境(LexicalEnvironment)并将其保存在作用域链中。 -
进入执行上下文
当进入执行块级作用域(包括 for、if、while 和 switch 等语句块)后,会创建一个新的词法环境。如果执行块级作用域中包含 let 变量声明语句,这些变量将被添加到这个词法环境的环境记录中。 -
绑定变量值
运行到 let 定义的变量时,JavaScript 引擎会在当前词法环境中搜索该变量。首先在当前环境记录中找到这个变量,如果不存在变量,则向包含当前环境的外部环境记录搜索变量,直到全局作用域为止。如果变量值没有被绑定,JavaScript 引擎会将其绑定为 undefined,否则继续执行其他操作。 -
实现块级作用域
使用 let 定义变量时,在运行时不会在当前作用域之外创建单独的执行上下文,而是会创建子遮蔽(shadowing)新环境。在子遮蔽的词法环境中,变量的值只在最接近的块级作用域内有效。
const 的底层实现过程
const 具有与 let 相同的底层实现原理,区别在于 const 定义的变量被视为常量(在赋值之后无法更改),因此变量声明时 必须初始化。此外,应该注意的是,使用 const 声明的对象是可以修改属性的。在定义 const 对象时,对象本身是常量,而不是对象的属性。只有对象本身不能被修改,而对象包含的属性可以任意修改。
let和const有暂时性死区
有点类似webcomponent的shadowDom
二、Proxy
在 ES6 中,Proxy
是一种用于创建对象代理的机制,可以拦截并自定义对目标对象的各种操作,如属性访问、赋值、函数调用等。它提供了一种强大的方式来实现面向切面编程(AOP)和元编程。
Proxy
对象由两部分组成:目标对象(target)和处理程序对象(handler)。处理程序对象定义了各种捕获器(trap)方法,这些方法会在对目标对象进行特定操作时被调用。
1、代码实例
以下是一些使用 Proxy
的示例:
- 拦截属性访问:
const target = {
name: 'John',
age: 30
};
const handler = {
get: function(target, prop) {
console.log(`Accessing property "${prop}"`);
return target[prop];
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // Accessing property "name", then prints "John"
- 拦截属性赋值:
const target = {
count: 0
};
const handler = {
set: function(target, prop, value) {
console.log(`Setting property "${prop}" to "${value}"`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
proxy.count = 10; // Setting property "count" to "10"
console.log(proxy.count); // 10
- 拦截函数调用:
const target = {
add: function(a, b) {
return a + b;
}
};
const handler = {
apply: function(target, thisArg, args) {
console.log(`Calling function with arguments ${args}`);
return target.apply(thisArg, args);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.add(3, 4)); // Calling function with arguments [3,4], then prints 7
通过使用 Proxy
,可以在不修改原始对象的情况下,对对象的行为进行灵活的控制和扩展。
三、 Reflect
在 ES6 中,Reflect
是一个内置的对象,它提供了一组与对象操作相关的静态方法,这些方法与传统的对象操作方法类似
,但更加规范化和统一。Reflect
的目的是为了提供一种更加优雅和一致的方式来执行对象操作,并且可以与 Proxy
对象一起使用,实现对对象操作的拦截和自定义。
1、常见方法及示例
-
Reflect.get(target, propertyKey[, receiver])
:获取对象的属性值。const obj = { name: 'John', age: 30 }; console.log(Reflect.get(obj, 'name')); // John
-
Reflect.set(target, propertyKey, value[, receiver])
:设置对象的属性值。const obj = { name: 'John' }; Reflect.set(obj, 'age', 30); console.log(obj); // { name: 'John', age: 30 }
-
Reflect.has(target, propertyKey)
:检查对象是否具有某个属性。const obj = { name: 'John', age: 30 }; console.log(Reflect.has(obj, 'name')); // true
-
Reflect.apply(target, thisArgument, argumentsList)
:以指定的this
值和参数列表调用一个函数。function add(a, b) { return a + b; } console.log(Reflect.apply(add, null, [3, 4])); // 7
-
Reflect.construct(target, argumentsList[, newTarget])
:使用指定的构造函数和参数列表创建一个新对象。class Person { constructor(name, age) { this.name = name; this.age = age; } } const person = Reflect.construct(Person, ['John', 30]); console.log(person); // Person { name: 'John', age: 30 }
Reflect
的方法与传统的对象操作方法类似,但在一些情况下更加灵活和安全,特别是在与 Proxy
对象一起使用时,可以实现对对象操作的拦截和自定义。
四、class语法
JavaScript 的类最终也是一种函数,使用 class 关键字创建的类会被编译成一个函数,因此其底层原理与函数有一些相似之处。
1、js中原型继承实现继承有哪些方法?
构造器借用、寄生式继承、组合继承
2、class语法继承,最接近哪种?
寄生+组合
js引擎将class语法转化为了原型链继承的方式
3、构造函数
使用 class 关键字来定义类时,在内部会创建一个特殊的函数,称为构造函数(constructor)。构造函数用于在创建对象时初始化对象的属性,类似于传统的基于原型的代码中的构造函数。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const p = new Person("Tom", 20);
console.log(p.name, p.age); // Tom 20
class 中定义的属性和方法分别定义在这个构造函数的 prototype 属性中。并且与原型方式不同的是,类的方法(静态方法)是不可枚举的
,因此无法使用 for…in 循环进行遍历。
问题:是否可枚举
判断方法:
是否可枚举,就用for…in循环遍历到属性,不能遍历就不可枚举
如何设置不可枚举:
Object.defineProperty(obj,'name',{
value:'frank',
enumerable: false // 不可枚举
})
for (const key in obj) {
console.log(key); // 没有输出
}
4、继承
class Student extends Person {
constructor(name,age,grade){
super(name,age);
this.grade = grade;
}
}
这段代码中,子类 Student 继承了父类 Person 的构造函数方法并添加了自己的属性和方法。
5、静态方法和属性
类中的静态方法和属性可以使用 static 关键字来定义,它们不是类实例的属性,而是类本身的属性和方法。
class Person {
static species = "human";
static saySpecies() {
console.log(`We are ${this.species}.`);
}
}
这段代码中定义了一个静态方法和一个静态属性,可以通过类本身直接调用静态方法和属性。
6、也可以使用class表达式创建函数对象
const Person = class {
constructor(name,age){
this.name = name;
this.age = age;
}
}
五、@修饰器(Decorators)
ES6 修饰器是一种特殊的语法,它可以用来修改类及其成员(方法、属性、访问器等)的行为。修饰器本质上是一个函数,它接收被修饰的目标、名称和描述符作为参数,并返回一个新的描述符或直接修改目标。
修饰器可以用于实现许多功能,如日志记录、性能测量、权限控制等,它可以帮助我们在不修改原有代码的情况下增强类的功能。
1、代码实例
-
方法修饰器
function logMethod(target, name, descriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args) { console.log(`Calling method ${name} with arguments ${args}`); const result = originalMethod.apply(this, args); console.log(`Method ${name} returned ${result}`); return result; }; return descriptor; } class MyClass { @logMethod myMethod(arg1, arg2) { return arg1 + arg2; } } const instance = new MyClass(); instance.myMethod(2, 3);
在这个例子中,
logMethod
修饰器用于在方法调用前后输出日志信息。 -
类修饰器
function logClass(constructor) { console.log(`Creating class ${constructor.name}`); return constructor; } @logClass class MyClass { constructor() { console.log('Constructing MyClass instance'); } } const instance = new MyClass();
这里的
logClass
修饰器在创建类的时候输出日志信息。
2、注意:
修饰器只可用于类和类属性方法,不可用于函数,因为存在函数提升。
修饰器是在编译时执行而不是运行时,所以修饰符是可以在编译时执行的代码,优秀哦
六、箭头函数
在箭头函数中,this关键字的作用域与它周围代码作用域相同,因而有时也被称为“词法作用域函数”。
1、基础概念与使用
箭头函数的原理是基于JavaScript中的闭包、this和参数作用域。在箭头函数中,this关键字始终指向函数所在上下文的this指针,而不是所在作用域的this指针。
2、箭头函数与普通函数的区别
- this指向,箭头函数不能定义构造器
- 不能new
- 内部无arguments对象
- this绑定方法失效,如apply、call、bind
所以,当我们需要引用所在父级的this指向时,可以用箭头函数。