ES6进阶知识一

62 阅读10分钟

一、ES6构建工具与模块化

1.1.构建工具

ES6的构建工具包括Gulp、Babel、Webpack等。

这些工具可以帮助开发者将ES6代码编译为ES5代码,以便在旧版浏览器上运行。

它们还支持代码监听、打包、压缩等功能,提高开发效率和代码质量。

1.1.1.Webpack

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。它会根据模块的依赖关系进行静态分析,然后将这些模块打包成一个或多个 bundle。

安装 Webpack

在项目根目录下运行以下命令来安装 Webpack 及其 CLI 工具:

npm install --save-dev webpack webpack-cli

配置 Webpack

在项目根目录下创建一个 webpack.config.js 文件,并添加以下内容:

const path = require('path');

module.exports = {
  entry: './src/index.js', // 入口文件
  output: {
    filename: 'bundle.js', // 输出文件名
    path: path.resolve(__dirname, 'dist') // 输出路径
  },
  module: {
    rules: [
      {
        test: /.js$/, // 匹配所有 .js 文件
        exclude: /node_modules/, // 排除 node_modules 目录
        use: {
          loader: 'babel-loader', // 使用 Babel 加载器
          options: {
            presets: ['@babel/preset-env'] // 配置 Babel 预设
          }
        }
      }
    ]
  }
};

使用 Webpack

在 package.json 文件的 scripts 部分添加一个构建脚本

"scripts": {
  "build": "webpack --config webpack.config.js"
}

然后运行以下命令来构建项目:

npm run build

1.1.2.Babel

Babel 是一个广泛使用的 JavaScript 编译器,可以将 ES6+ 代码转换为向后兼容的 JavaScript 代码,以便在旧版浏览器或环境中运行。

安装 Babel

在项目根目录下运行以下命令来安装 Babel 及其预设:

npm install --save-dev @babel/core @babel/cli @babel/preset-env

配置 Babel

在项目根目录下创建一个 .babelrc 文件,并添加以下内容:

{
  "presets": ["@babel/preset-env"]
}

或者使用 babel.config.json 文件(两者选其一):添加内容一样

{
  "presets": ["@babel/preset-env"]
}

1.2.ES6模块化

ES6 引入了模块化编程的概念,允许我们将代码拆分成多个模块,每个模块只关注自己的功能,并通过 import 和 export 关键字来实现模块之间的通信。

1.命名导出导入

导出模块

在 src/math.js 文件中定义一些数学函数,并使用命名导出:

// src/math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

导入模块

在 src/index.js 文件中导入 math.js 模块中的函数

// src/index.js
import { add, subtract } from './math.js';

console.log(add(2, 3)); // 输出: 5
console.log(subtract(5, 3)); // 输出: 2

2. 默认导出与导入

导出模块

在 src/utils.js 文件中定义一个工具函数,并使用默认导出:

// src/utils.js
export default function logMessage(message) {
  console.log(message);
}

导入模块

在 src/index.js 文件中导入 utils.js 模块中的默认函数:

// src/index.js
import logMessage from './utils.js';

logMessage('Hello, ES6 Modules!'); // 输出: Hello, ES6 Modules!

1.3.完整案例展示

1. 项目结构

my-es6-project/
├── dist/
│   └── bundle.js (构建后生成的文件)
├── node_modules/ (安装的依赖包)
├── src/
│   ├── index.js
│   ├── math.js
│   └── utils.js
├── .babelrc (Babel 配置文件)
├── package.json (项目配置文件)
└── webpack.config.js (Webpack 配置文件)

2. 代码实现

src/math.js

export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

src/utils.js

export default function logMessage(message) {
  console.log(message);
}

src/index.js

import { add, subtract } from './math.js';
import logMessage from './utils.js';

console.log(add(2, 3)); // 输出: 5
console.log(subtract(5, 3)); // 输出: 2
logMessage('Hello, ES6 Modules!'); // 输出: Hello, ES6 Modules!

.babelrc

{
  "presets": ["@babel/preset-env"]
}

webpack.config.js

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
};

package.json

{
  "name": "my-es6-project",
  "version": "1.0.0",
  "description": "A demo project for ES6 modules and build tools.",
  "main": "index.js",
  "scripts": {
    "build": "webpack --config webpack.config.js"
  },
  "devDependencies": {
    "@babel/core": "^7.x.x",
    "@babel/cli": "^7.x.x",
    "@babel/preset-env": "^7.x.x",
    "webpack": "^5.x.x",
    "webpack-cli": "^4.x.x"
  }
}

3. 构建与运行

在项目根目录下运行以下命令来构建项目

npm run build

构建成功后,将在 dist 目录下生成一个 bundle.js 文件。你可以通过创建一个 HTML 文件来引用这个构建后的文件,并在浏览器中查看输出结果

二、高阶异步编程模式

2.1.任务队列与微任务

1.任务队列(Task Queue):

通常包含宏任务(MacroTask),如setTimeoutsetInterval、I/O操作等。

宏任务之间的执行间隔可能会受到浏览器渲染和其他因素的影响。

2.微任务队列(Microtask Queue):

包含微任务(MicroTask),如Promise的回调、MutationObserver等。

微任务的优先级高于宏任务,会在当前宏任务执行完毕后立即执行,直到微任务队列为空。

3.宏任务与微任务的执行顺序

console.log('宏任务1');

setTimeout(() => {
  console.log('宏任务2');
}, 0);

Promise.resolve().then(() => {
  console.log('微任务1');
});

console.log('宏任务3');

执行结果:

宏任务1

宏任务3

微任务1

宏任务2

解释:

首先执行同步代码(宏任务1和宏任务3)。

然后执行微任务(微任务1),因为微任务的优先级高于宏任务。

最后执行宏任务队列中的setTimeout回调(宏任务2)。

2.2.自定义事件与事件总线

自定义事件和事件总线是JavaScript中用于组件或对象间通信的重要机制。

1.自定义事件

允许在对象或应用程序的不同部分之间传递消息和数据。

可以通过CustomEvent构造函数或Event构造函数(在某些情况下)来创建自定义事件。

2.事件总线模式

是一种松耦合的通信方式,允许不同的模块或组件通过事件进行通信,而无需直接相互引用。

使用事件总线可以减少模块之间的依赖,提高代码的可维护性和可扩展性。

在Vue等前端框架中,自定义事件和事件总线得到了广泛的应用。例如,在Vue中,子组件可以通过$emit触发自定义事件,父组件则可以通过v-on@语法来监听这些事件,从而实现组件间的通信。

3.使用事件总线进行模块间通信

假设我们有两个模块moduleAmoduleB,它们需要通过事件总线进行通信。

// 事件总线类
class EventBus {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  publish(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(...args));
    }
  }
}

// 创建事件总线实例
const eventBus = new EventBus();

// moduleA
function moduleA() {
  eventBus.subscribe('dataReceived', (data) => {
    console.log('Module A received data:', data);
  });
}

// moduleB
function moduleB() {
  setTimeout(() => {
    eventBus.publish('dataReceived', { key: 'value' });
  }, 1000);
}

// 初始化模块
moduleA();
moduleB();

执行结果:

在1秒后,控制台会输出“Module A received data: { key: 'value' }”。

解释:

moduleA通过eventBus.subscribe订阅了dataReceived事件。

moduleB在1秒后通过eventBus.publish发布了dataReceived事件,并传递了一个数据对象。

moduleA的回调函数被调用,并接收到了传递的数据

2.3.异步迭代与生成器的高级用法

异步迭代器和生成器是ES6中引入的高级异步编程特性,它们允许开发者以更直观、更优雅的方式处理异步操作。

  1. 异步迭代器

    • 允许在异步操作中逐个处理数据项,而不会阻塞程序的执行。
    • 它们通过Symbol.asyncIterator方法实现,可以被for await...of循环使用。
  2. 异步生成器函数

    • 可以暂停和恢复执行,以异步方式生成数据。
    • 它们使用async function*声明,并通过yield关键字返回数据。

异步迭代器和生成器在处理大量数据或复杂异步操作时非常有用。例如,它们可以用于读取大型文件、处理网络请求流等场景。通过异步迭代器和生成器,开发者可以更加灵活地控制异步操作的执行流程,从而提高代码的可读性和可维护性。

3.示例

使用异步迭代器处理网络请求流

假设我们需要从一个API端点获取分页数据,并逐个处理这些数据项

async function* fetchData(url, pageSize) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}&pageSize=${pageSize}`);
    const data = await response.json();
    if (data.length === 0) break; // 假设返回的数据为空数组时表示没有更多数据
    for (const item of data) {
      yield item;
    }
    page++;
  }
}

(async () => {
  const dataStream = fetchData('https://api.example.com/items', 10);
  for await (const item of dataStream) {
    console.log('Received item:', item);
    // 在这里处理每个数据项,例如保存到数据库或更新UI
  }
  console.log('All data processed.');
})();

执行结果:

控制台会逐个输出从API获取的数据项,并在处理完所有数据后输出“All data processed.”。

解释:

fetchData是一个异步生成器函数,它使用fetch函数从API获取分页数据。

while循环中,它不断请求下一页的数据,直到返回的数据为空数组。

对于每一页的数据,它使用for...of循环逐个yield出数据项。

在异步函数块中,我们使用for await...of循环来消费这个异步生成器,并逐个处理从API获取的数据项。

三、迭代器(Iterator)与生成器(Generator)

3.1.迭代器(Iterator)

1.定义

迭代器是一种对象,它提供了一种顺序访问集合中每个元素的方式,而不暴露集合内部的表示。

迭代器的主要方法是next(),每次调用该方法都会返回一个包含valuedone属性的对象。value表示当前元素的值,done表示是否已经遍历完所有元素。

2.实现原理

在需要进行遍历操作时,通过调用集合对象上的Symbol.iterator方法获取到该集合对象对应的默认迭代器。

在每次调用next()方法时,迭代器会执行相应的操作,并返回一个包含valuedone属性的对象。

如果donefalse,则表示还有更多的元素需要遍历,此时value属性表示当前遍历到的值。

如果donetrue,则表示已经遍历完所有元素,此时value属性为undefined

3.使用场景

迭代器提供了一种统一的遍历机制,使得我们可以使用相同的方式来访问不同类型的数据结构。

无论是数组、字符串、Set、Map还是自定义对象,只要实现了迭代器接口,就可以使用for...of循环或者手动调用next()方法来进行遍历。

4.示例:

let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

3.2.生成器(Generator)

1.定义:

生成器是一种特殊的函数,它可以通过yield关键字来暂停函数的执行,并返回一个包含valuedone属性的对象。

生成器函数使用function*语法进行定义。

2.实现原理:

当调用生成器函数时,实际上并不会立即执行函数体内部的代码,而是返回一个迭代器对象,该迭代器对象实现了next()方法。

每次调用next()方法时,生成器会从上一次暂停的位置继续执行代码,直到遇到下一个yield关键字或者函数结束。

3.使用场景:

生成器提供了一种更灵活、更可控的方式来处理异步编程。

通过使用yield关键字,我们可以在函数执行过程中暂停和恢复,并且可以将异步操作以同步方式编写和理解。

4.示例:

function* generatorFunc() {
    yield 'Hello';
    yield 'World';
}
let generator = generatorFunc();
console.log(generator.next()); // { value: 'Hello', done: false }
console.log(generator.next()); // { value: 'World', done: false }
console.log(generator.next()); // { value: undefined, done: true }

3.3.迭代器与生成器的关系

生成器本身就是一个迭代器,它返回的迭代器对象实现了迭代器接口,因此可以使用next()方法进行遍历。

生成器通过yield关键字实现了函数的暂停和恢复,这使得它在处理异步编程时具有更大的灵活性。

3.4.总结

迭代器为JavaScript提供了一种统一的遍历机制,使得我们可以使用相同的方式来访问不同类型的数据结构。

生成器则通过yield关键字实现了函数的暂停和恢复,为异步编程提供了更自然和易于理解的方式。

在实际开发中,迭代器与生成器经常结合使用,以实现更复杂的迭代和异步逻辑。

四、代理(Proxy)与反射(Reflect)

4.1.代理(Proxy)

  1. 定义与功能

    • 代理是ES6引入的一种元编程机制,允许开发者拦截并自定义目标对象的操作。
    • 通过使用Proxy构造函数,开发者可以创建一个代理对象,该对象将拦截对目标对象的读取、写入、函数调用等操作。
  2. 创建与使用

    • 要创建一个代理对象,需要使用Proxy构造函数,并传入两个参数:目标对象(target)和处理程序(handler)。
    • 处理程序是一个对象,其方法被称为陷阱(trap),用于指定拦截后的行为。
  3. 常用陷阱方法

    • get(target, property, receiver):拦截对目标对象属性的读取操作。
    • set(target, property, value, receiver):拦截对目标对象属性的写入操作。
    • apply(target, thisArg, argumentsList):拦截对目标对象的函数调用操作。
    • construct(target, argumentsList, newTarget):拦截对目标对象的构造函数调用操作。
    • 其他陷阱方法还包括hasdeletePropertygetOwnPropertyDescriptor等。
  4. 示例

const target = { name: 'John', age: 30 };
const handler = {
  get(target, property) {
    console.log(`Getting property: ${property}`);
    return target[property];
  },
  set(target, property, value) {
    console.log(`Setting property: ${property} = ${value}`);
    target[property] = value;
  }
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出: Getting property: name John
proxy.age = 35; // 输出: Setting property: age = 35

4.2.反射(Reflect)

  1. 定义与功能

    • 反射是ES6引入的一个内置对象,提供了一套与JavaScript内置操作直接对应的静态方法。
    • 这些方法的设计目的是为了使操作对象的行为变得更为清晰、更易于使用,并在某些情况下提供更好的错误处理机制。
  2. 常用方法

    • Reflect.get(target, propertyKey[, receiver]):获取目标对象上指定属性的值。
    • Reflect.set(target, propertyKey, value[, receiver]):设置目标对象上指定属性的值。
    • Reflect.defineProperty(target, propertyKey, attributes):在目标对象上定义一个属性。
    • Reflect.deleteProperty(target, propertyKey):尝试删除目标对象上的指定属性。
    • Reflect.apply(target, thisArgument, argumentsList):调用目标函数。
    • Reflect.construct(target, argumentsList[, newTarget]):使用给定的参数列表调用构造函数并创建一个新实例。
    • 其他方法还包括Reflect.hasReflect.ownKeysReflect.isExtensible等。
  3. 特点

    • Reflect对象的方法与Object对象上的同名方法相对应,但Reflect方法通常返回一个布尔值以表示操作是否成功,而不是静默失败。
    • Reflect方法在遇到非法操作时会抛出适当的异常,如TypeError或RangeError,而不是默默地失败。
  4. 示例

const obj = { a: 1, b: { c: 2 } };
console.log(Reflect.get(obj, 'a')); // 输出: 1
Reflect.set(obj, 'a', 2);
console.log(obj.a); // 输出: 2

4.3.代理与反射的关系

代理和反射是协调合作的关系。代理用于拦截对象的内置操作,并注入自定义的逻辑;而反射则向外界暴露了一些底层操作的默认行为,使得开发者可以更方便地访问和修改这些行为。

在代理的陷阱方法中,通常会使用Reflect对象的方法来执行原始的、未被拦截的操作。这样,开发者就可以在自定义逻辑前后添加额外的行为,而不会破坏原始的操作。

五、元编程与装饰器

5.1.元编程

  1. Symbol

    • 引入了一种新的原始数据类型,用于创建唯一的标识符。
    • Symbols 可以被用作对象的键,且保证不会和现有的字符串键冲突。
    • Symbols 提供了一种隐藏层,使得对象可以拥有不可迭代且不能被现有反射工具获取的属性。
  2. Reflect

    • 是一个新的全局对象,提供了大量有用的内省(introspection)方法。
    • Reflect 囊括了所有JavaScript引擎内部专有的“内部方法”,现在被暴露为了一个单一、方便的对象。
    • 通过Reflect对象,开发者可以更容易地访问和操作对象的底层信息。
  3. Proxy

    • 允许开发者创建对象的代理,并拦截和自定义对目标对象的操作。
    • 通过Proxy,开发者可以实现对对象行为的动态修改和扩展。

5.2.装饰器

装饰器是ES7(虽然目前在标准中尚未正式定稿,但已被广泛使用)提出来的一种语法特性,它允许你在类、类方法、类属性等声明前面添加特殊的修饰符,以此来修改他们的行为。装饰器本质上是一个高阶函数,用于对类的定义、方法、属性进行修饰。

  1. 装饰器的语法

    • 使用@符号作为前缀,后面紧跟装饰器函数的名称。
    • 装饰器函数接受特定的参数,如目标对象、属性名、属性描述符等,并返回一个新的属性描述符或进行其他操作。
  2. 装饰器的应用

    • 类装饰器:应用于整个类,可以修改类的构造函数或添加新的静态方法。
    • 方法装饰器:应用于类的方法,可以修改方法的行为或添加额外的逻辑。
    • 属性装饰器:应用于类的属性,可以修改属性的特性或添加元数据。
  3. 装饰器的用途

    • 日志记录:为类或方法自动添加日志输出,便于调试。
    • 权限控制:控制方法的访问权限,检查调用者的身份。
    • 缓存功能:对函数调用结果进行缓存,避免重复计算。
    • 性能监控:通过装饰器监控函数执行的性能数据。
    • 自动绑定:为类方法自动绑定this,避免在不同上下文中丢失this指向。
    • 代码复用:通过装饰器复用代码,不必重复编写逻辑。
    • 提升可读性:通过分离业务逻辑与装饰器逻辑,提升代码可读性和可维护性。
  4. 装饰器的使用示例

function log(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function(...args) {
    console.log(`Calling ${name} with`, args);
    return original.apply(this, args);
  };
  return descriptor;
}

class Example {
  @log
  sayHello(name) {
    return `Hello, ${name}`;
  }
}

const example = new Example();
example.sayHello('World'); // 输出: Calling sayHello with [ 'World' ] Hello, World

在这个例子中,log装饰器为sayHello方法添加了日志输出功能,每次调用sayHello时都会记录参数信息。

六、Generator 函数与class

6.1.Generator 函数

Generator 函数允许你暂停和恢复函数的执行

1. 定义 Generator 函数

Generator 函数使用 function* 语法来定义,而不是普通的 function 关键字。

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

2. 调用 Generator 函数

调用 Generator 函数不会立即执行其代码,而是返回一个迭代器对象。

const gen = myGenerator();

3. 使用 next() 方法

通过调用迭代器对象的 next() 方法,可以逐步执行 Generator 函数中的代码。每次调用 next() 方法时,Generator 函数会从上次暂停的地方继续执行,直到遇到下一个 yield 表达式或函数结束

console.log(gen.next().value); // 输出: 1
console.log(gen.next().value); // 输出: 2
console.log(gen.next().value); // 输出: 3
console.log(gen.next().done);  // 输出: true

每次调用 next() 方法时,该方法返回一个对象,该对象有两个属性:

valueyield 表达式的值(如果函数已结束,则为 undefined)。

done:一个布尔值,表示 Generator 函数是否已经执行完毕。

4. 示例:使用 Generator 函数处理异步操作

Generator 函数特别适合用于处理异步操作,因为它允许你暂停和恢复函数的执行,而不需要嵌套回调函数或 Promise 链。以下是一个简单的示例,展示了如何使用 Generator 函数和 Promise 处理异步操作:

function fetchData(url) {
  return new Promise((resolve, reject) => {
    // 模拟异步数据获取
    setTimeout(() => {
      resolve(`Data from ${url}`);
    }, 1000);
  });
}

function* asyncFlow() {
  const data1 = yield fetchData('https://api.example.com/data1');
  console.log(data1);

  const data2 = yield fetchData('https://api.example.com/data2');
  console.log(data2);
}

function runGenerator(generator) {
  const it = generator();

  function handleResult(result) {
    if (result.done) {
      return;
    }

    result.value.then(
      (value) => {
        const nextResult = it.next(value);
        handleResult(nextResult);
      },
      (error) => {
        console.error(error);
      }
    );
  }

  handleResult(it.next());
}

runGenerator(asyncFlow);

在这个示例中,asyncFlow 是一个 Generator 函数,它使用 yield 来暂停执行并等待异步操作的结果。runGenerator 函数负责运行这个 Generator 函数,并在每次 yield 表达式的结果解决后继续执行。

5. 注意事项

Generator 函数返回的是一个迭代器对象,而不是普通的函数返回值。

Generator 函数中的 yield 表达式可以返回任何值,包括对象、数组、Promise 等。

Generator 函数允许你使用 yield* 表达式来委托另一个 Generator 函数或可迭代对象。

6.2.class

class 语法被引入,为JavaScript提供了更接近传统面向对象编程的语法糖。尽管JavaScript本质上仍然是基于原型的继承,但class语法使得定义和继承类变得更加直观和易于理解。

1. 定义类

使用class关键字来定义一个类。类体中包含构造器(constructor)和方法。

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

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

2. 创建类的实例

使用new关键字来创建类的实例。

const person1 = new Person('Alice', 30);
person1.greet(); // 输出: Hello, my name is Alice and I am 30 years old.

3. 继承

使用extends关键字来实现类的继承。子类可以重写父类的方法,或者添加新的方法。

class Employee extends Person {
  constructor(name, age, jobTitle) {
    super(name, age); // 调用父类的构造器
    this.jobTitle = jobTitle;
  }

  greet() {
    super.greet(); // 调用父类的方法
    console.log(`I am an ${this.jobTitle}.`);
  }
}

const employee1 = new Employee('Bob', 40, 'Engineer');
employee1.greet();
// 输出:
// Hello, my name is Bob and I am 40 years old.
// I am an Engineer.

4. 静态方法

使用static关键字来定义静态方法。静态方法属于类本身,而不是类的实例。

class MathUtils {
  static add(a, b) {
    return a + b;
  }
}

console.log(MathUtils.add(2, 3)); // 输出: 5

5. Getter 和 Setter

在类中可以使用getset关键字来定义属性的getter和setter方法。

class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  get area() {
    return this._width * this._height;
  }

  set area(value) {
    // 注意:这里通常不会直接设置面积,因为面积是由宽度和高度决定的。
    // 但为了演示,我们可以抛出一个错误。
    throw new Error('Area is a read-only property.');
  }

  // 可以设置宽度和高度
  set width(value) {
    this._width = value;
  }

  get width() {
    return this._width;
  }

  // 同理,可以设置和获取高度
  set height(value) {
    this._height = value;
  }

  get height() {
    return this._height;
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.area); // 输出: 50
// rect.area = 100; // 这将抛出错误
rect.width = 20;
console.log(rect.area); // 输出: 100

注意事项

尽管ES6引入了class语法,但JavaScript的类并不完全等同于其他面向对象语言中的类。例如,JavaScript的类没有传统的“类型”或“接口”的概念,也没有访问修饰符(如privateprotected等,尽管在后续版本中引入了私有字段)。

类的继承是基于原型的,这意味着子类会继承父类的原型链上的属性和方法。

在类中,this的绑定是自动的,与在函数或方法中使用this时需要小心的情况不同。

class语法为JavaScript提供了更清晰、更易于理解的面向对象编程方式,但它仍然是基于JavaScript原型继承机制的语法糖。

亲爱的友友们~~码字不易,给孩子点点赞呗