一、let 和 const
let
- 内存特性:let声明的变量后所分配的存储空间里的值(如:数字、字符串、数组等)可以被改变,存储的指针可变更、指针指向的内存空间也可被改变也可改变(如:数组、对象等)
- 作用域: 块级作用域
- 暂时性死区:在声明之前使用会产生从而报错
- 变量提升:不存在变量提升,不可重复声明
- for循环:JavaScript 引擎内部会记住上一轮循环 ,初始化本轮let声明的变量
const
- 内存特性:const声明变量后所分配的存储空间里的值(如:数字、字符串、数组等)等同于常量不可改变,存储的指针不可改变,指针指向的内存空间里的值可改变(如:数组、对象等)
- 作用域、暂存行死区、变量提升特性与let相同
// 正确示例 const obj = {} obj.father = '123' // 错误示例 const obj = {} obj = { father: '123'} // 冻结对象 const obj = { name: 'Alice', age: 20 }; Object.freeze(obj); obj.name = 'Bob'; // 抛出TypeError错误
顶层对象
- 浏览器里面,顶层对象是
window
,但 Node 和 Web Worker 没有window
。- 浏览器和 Web Worker 里面,
self
也指向顶层对象,但是 Node 没有self
。- Node 里面,顶层对象是
global
,但其他环境都不支持。
二、解构赋值和扩展
解构赋值
- 解构赋值支持对象:对象、数组、字符串、布尔值、函数
- 解构赋值支持设置默认值:当赋值对象严格等于undefined,默认值才生效
- 对象的结构赋值内部机制,先找到同名属性,然后再赋值给对应的变量,真正被复制的是后者,而不是前者,该赋值过程“前者”称为“模式”,“后者”称为“变量”
- 结构赋值的过程不能使用圆括号的三种场景:
- 变量声明语句
- 函数语句中的参数
- 赋值语句中的模式
// 对象 const node = { loc: { start: { line: 1, column: 5 } } }; let { loc, loc: { start }, loc: { start: { line }} } = node; line // 1 loc // Object {start: Object} start // Object {line: 1, column: 5} // 数组 const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o" // 字符串 let {length : len} = 'hello'; len // 5 // 布尔类型 let {toString: s} = true; s === Boolean.prototype.toString // true // 函数 function add([x, y]){ return x + y; } add([1, 2]); // 3
函数的扩展
- 函数的参数可以设置默认值 ,例:
function fn(a,b = 1) {} // 默认值作用域指向外层对象 let foo = 'outer'; function bar(func = () => foo) { let foo = 'inner'; console.log(func()); } bar(); // outer
- 与解构赋值的默认值结合使用
// 这种写法函数第二个参数不能省略 function fetch(url, { body = '', method = 'GET', headers = {} }) { console.log(method); } fetch('http://example.com', {}) // "GET" fetch('http://example.com') // 报错 // 这种写法函数第二个参数可以省略 function fetch(url, { body = '', method = 'GET', headers = {} } = {}) { console.log(method); } fetch('http://example.com') // "GET"
- rest参数
const obj = [1,2,3,4] console.log(...obj) // 1,2,3,4
- 箭头函数
- 对于普通函数来说,内部的
this
指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this
对象,内部的this
就是定义时上层作用域中的this
。也就是说,箭头函数内部的this
指向是固定的,相比之下,普通函数的this
指向是可变的。- 箭头函数实际上可以让
this
指向固定化,绑定this
使得它不再可变,这种特性很有利于封装回调函数。// 只有一条语句且返回值不为对象时,不需要return且可以省略代码块 const sum = (num_a, num_b) => num_a + num_b // 只有一条语句时且返回值为对象时, 需要用圆括号 const getObj = id => ({ id: id, name: Tom }) // 不报错 const getObj = id => { id: id, name:Tom } // 报错 // 箭头函数this指向问题 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42
数组的扩展
- 扩展运算符 :
console.log(...[1, 2, 3]) // 1 2 3
- 扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符
- 对象的扩展运算符等同于使用
Object.assign()
方法let aClone = { ...a }; // 等同于 let aClone = Object.assign({}, a);
三、Proxy、Promise、Reflect
proxy
- Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
- ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
var proxy = new Proxy({}, { get: function(target, propKey) { return 35; } }); proxy.time // 35 proxy.name // 35 proxy.title // 35
- 手撕Proxy:
const target = { name: 'Tom', age: 20 }; const handler = { get: function(target, prop) { console.log(`Getting ${prop} property`); return target[prop]; }, set: function(target, prop, value) { console.log(`Setting ${prop} property to ${value}`); target[prop] = value; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // 输出:Getting name property Tom proxy.age = 25; // 输出:Setting age property to 25 console.log(proxy.age); // 输出:Getting age property 25
Promise
- Promise 是一种异步编程模式,它允许你在 JavaScript 中处理异步操作。Promise 对象表示一个异步操作的最终完成或失败,并且可以使用 then() 方法来处理异步操作的结果。Promise 可以帮助你避免回调地狱,使异步代码更加清晰和易于维护。
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。- 手撕Promise:
function MyPromise(fn) { const self = this; self.value = null; self.error = null; self.onFulfilled = null; self.onRejected = null; function resolve(value) { setTimeout(() => { self.value = value; self.onFulfilled(self.value); }, 0); } function reject(error) { setTimeout(() => { self.error = error; self.onRejected(self.error); }, 0); } fn(resolve, reject); } MyPromise.prototype.then = function(onFulfilled, onRejected) { const self = this; return new MyPromise((resolve, reject) => { self.onFulfilled = function(value) { try { const result = onFulfilled(value); resolve(result); } catch (error) { reject(error); } }; self.onRejected = function(error) { try { const result = onRejected(error); resolve(result); } catch (error) { reject(error); } }; }); }; const promise = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('Hello, world!'); }, 1000); }); promise.then( value => console.log(value), error => console.error(error) );
Reflect
- 在 JavaScript 中,对象的操作方法比较分散,例如获取属性值可以使用 obj[key]、obj.property、Object.getOwnPropertyDescriptor() 等方法,这些方法的名称和用法都不一样,不太方便记忆和使用。Reflect 提供了一组统一的方法,例如 Reflect.get()、Reflect.set()、Reflect.getOwnPropertyDescriptor() 等,使得对象操作更加一致和易于使用。
- Reflect 提供了一组统一的 API,使得对象操作更加一致和易于使用,同时也支持 Proxy 拦截和提供了一些新的操作方法,从而提高了 JavaScript 的灵活性和可扩展性。
示例:
const obj = { name: 'Tom', age: 20 }; console.log(Reflect.get(obj, 'name')); // 输出:Tom Reflect.set(obj, 'age', 25); console.log(obj.age); // 输出:25 console.log(Reflect.has(obj, 'name')); // 输出:true Reflect.deleteProperty(obj, 'age'); console.log(obj.age); // 输出:undefined function Person(name, age) { this.name = name; this.age = age; } const person = Reflect.construct(Person, ['Tom', 20]); console.log(person.name, person.age); // 输出:Tom 20 function sayHello(name) { console.log(`Hello, ${name}!`); } Reflect.apply(sayHello, null, ['Tom']); // 输出:Hello, Tom!
四、Class 与 构造器
Class 类
- 类定义:使用class关键字定义类,类名通常采用大写字母开头的驼峰命名法。
- 构造函数:类中可以定义一个构造函数,使用constructor关键字定义,用于初始化对象的属性。
- 属性和方法:类中可以定义属性和方法,使用类似于对象字面量的语法,但是不需要使用逗号分隔。
- 继承:类可以通过extends关键字实现继承,子类可以继承父类的属性和方法,并且可以重写父类的方法。
- super关键字:子类中可以使用super关键字调用父类的构造函数和方法。
- 静态方法:类中可以定义静态方法,使用static关键字定义,静态方法可以直接通过类名调用,而不需要创建对象。
- getter和setter:类中可以定义getter和setter方法,用于获取和设置对象的属性值。
- 类表达式:类也可以使用表达式的方式定义,类表达式可以赋值给变量,也可以作为函数参数传递。
Generator函数
- 异步编程:Generator函数可以使用yield关键字暂停和恢复函数的执行,可以用于处理异步操作,避免回调地狱和多层嵌套的回调函数。
- 迭代器:Generator函数返回的是一个迭代器对象,可以用于遍历数据结构,例如数组、对象、Map等。
- 状态机:Generator函数可以用于实现状态机,每次调用next方法时,函数会从上一次暂停的位置继续执行,可以根据不同的状态返回不同的值。
- 控制流程:Generator函数可以用于控制流程,例如可以使用yield关键字暂停函数的执行,等待某个条件满足后再继续执行。
- 协程:Generator函数可以用于实现协程,可以在函数执行过程中暂停和恢复执行,可以用于处理一些复杂的任务。
- 用Generator和Promise模拟GPT文字输出的效果
function* gptOutput(text) { for (let i = 0; i < text.length; i++) { yield text[i]; yield new Promise(resolve => setTimeout(resolve, 50)); } } const output = gptOutput('Hello, world!'); const timer = setInterval(() => { const { value, done } = output.next(); if (done) { clearInterval(timer); } else { console.log(value); } }, 100);