前端宝典二十二:ES6必须掌握的功能和源码实现

在前端宝典二十一中,我们已经一起探讨了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 的底层实现过程

  1. 编译阶段
    在代码编译阶段,编译器会扫描整个函数体(或全局作用域),查找所有使用 let 定义的变量,为这些变量生成一个初始值为 undefined 的词法环境(LexicalEnvironment)并将其保存在作用域链中。

  2. 进入执行上下文
    当进入执行块级作用域(包括 for、if、while 和 switch 等语句块)后,会创建一个新的词法环境。如果执行块级作用域中包含 let 变量声明语句,这些变量将被添加到这个词法环境的环境记录中。

  3. 绑定变量值
    运行到 let 定义的变量时,JavaScript 引擎会在当前词法环境中搜索该变量。首先在当前环境记录中找到这个变量,如果不存在变量,则向包含当前环境的外部环境记录搜索变量,直到全局作用域为止。如果变量值没有被绑定,JavaScript 引擎会将其绑定为 undefined,否则继续执行其他操作。

  4. 实现块级作用域
    使用 let 定义变量时,在运行时不会在当前作用域之外创建单独的执行上下文,而是会创建子遮蔽(shadowing)新环境。在子遮蔽的词法环境中,变量的值只在最接近的块级作用域内有效。

const 的底层实现过程

const 具有与 let 相同的底层实现原理,区别在于 const 定义的变量被视为常量(在赋值之后无法更改),因此变量声明时 必须初始化。此外,应该注意的是,使用 const 声明的对象是可以修改属性的。在定义 const 对象时,对象本身是常量,而不是对象的属性。只有对象本身不能被修改,而对象包含的属性可以任意修改。

let和const有暂时性死区
有点类似webcomponent的shadowDom

二、Proxy

在 ES6 中,Proxy是一种用于创建对象代理的机制,可以拦截并自定义对目标对象的各种操作,如属性访问、赋值、函数调用等。它提供了一种强大的方式来实现面向切面编程(AOP)和元编程。

Proxy对象由两部分组成:目标对象(target)和处理程序对象(handler)。处理程序对象定义了各种捕获器(trap)方法,这些方法会在对目标对象进行特定操作时被调用。

1、代码实例

以下是一些使用 Proxy 的示例:

  1. 拦截属性访问:
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"
  1. 拦截属性赋值:
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
  1. 拦截函数调用:
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、常见方法及示例

  1. Reflect.get(target, propertyKey[, receiver]):获取对象的属性值。

    const obj = {
      name: 'John',
      age: 30
    };
    console.log(Reflect.get(obj, 'name')); // John
    
  2. Reflect.set(target, propertyKey, value[, receiver]):设置对象的属性值。

    const obj = {
      name: 'John'
    };
    Reflect.set(obj, 'age', 30);
    console.log(obj); // { name: 'John', age: 30 }
    
  3. Reflect.has(target, propertyKey):检查对象是否具有某个属性。

    const obj = {
      name: 'John',
      age: 30
    };
    console.log(Reflect.has(obj, 'name')); // true
    
  4. Reflect.apply(target, thisArgument, argumentsList):以指定的 this 值和参数列表调用一个函数。

    function add(a, b) {
      return a + b;
    }
    console.log(Reflect.apply(add, null, [3, 4])); // 7
    
  5. 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、代码实例

  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修饰器用于在方法调用前后输出日志信息。

  2. 类修饰器

    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指向时,可以用箭头函数。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值