Навигация по каталогу
- 17. Генератор
- 18. асинхронная функция
- 20. Класс
- 21. Модуль
- 22. Стиль программирования (оптимизация производительности)
17. Генератор
предоставлено ES6Решение для асинхронного программирования.грамматическиКонечный автомат, который инкапсулирует несколько внутренних состояний. Выполнение функции генераторавозвращает объект итератора. Это очень похоже на обещания, которые представляют собой контейнеры, содержащие результат события (обычно асинхронной операции), которое завершится в будущем.
Функция Генератор является обычной функцией, но имеет две характеристики.
1. Между ключевым словом функции и названием функции стоит звездочка (позиция не фиксирована);
2. Выражение yield используется внутри тела функции для определения различных внутренних состояний (yield означает «выход» на английском языке).
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next() // { value: 'hello', done: false }
hw.next()// { value: 'world', done: false }
hw.next()// { value: 'ending', done: true }
hw.next() // { value: undefined, done: true }
Функция имеет три состояния: привет, мир и оператор возврата (завершение выполнения). После вызова функции Генератор функция не выполняется, и возвращаемый результат является не результатом выполнения функции, аобъект-указатель на внутреннее состояние, который представляет собой объект Iterator, представленный в предыдущей главе. Далее должен быть вызван метод next объекта обходчика для перемещения указателя в следующее состояние (Выполнять оператор после yield до тех пор, пока не встретится оператор yield или return).
17.1, выражение доходности
Выражение yield — это флаг паузы. И используйте значение выражения сразу после yield в качестве значения атрибута value возвращаемого объекта. Выражение, следующее за выражением yield, выполняется только тогда, когда вызывается следующий метод и внутренний указатель указывает на инструкцию. Выражение yield имеет как сходства, так и различия с оператором return. Сходство заключается в том, что оба возвращают значение выражения, которое следует непосредственно за оператором. Разница в том, что каждый раз, когда встречается yield, функция приостанавливает выполнение и продолжает выполняться в обратном направлении с этой позиции в следующий раз, в то время как оператор return не имеетпамять местоположенияфункция.
Уведомление:
1. Выражение yield можно использовать только в функции Generator, а в других местах будет сообщено об ошибке.
2. Если выражение yield используется в другом выражении, оно должно быть заключено в круглые скобки.
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
3. Выражение yield используется в качестве параметра функции или помещается в правую часть выражения присваивания без круглых скобок.
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
Метод Symbol.iterator любого объекта равен функции генерации обходчика объекта, и вызов этой функции вернет объект обходчика объекта.
Функция Generator — это функция генерации обхода, поэтому генератор может быть назначен свойству объекта Symbol.iterator, чтобы объект имел интерфейс Iterator.
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
После выполнения функции Generator она возвращает объект-итератор. Сам объект также имеет свойство Symbol.iterator, которое возвращает себя после выполнения.
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g // true
17.2, параметры следующего метода
Само выражение yield не возвращает значение или всегда возвращает значение undefined. Следующий метод может принимать один параметр, который будет использоваться как возвращаемое значение предыдущего выражения yield.Семантически первый метод next используется для запуска объекта обходчика, поэтому никаких параметров не требуется.
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
console.log(g.next()) // { value: 0, done: false }
console.log (g.next()) // { value: 1, done: false }
console.log (.next(true) )// { value: 0, done: false } 执行i=-1,然后i++变成了0
Посмотрите на другой пример ниже
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()) // Object{value:6, done:false}
console.log(a.next()) // Object{value:NaN, done:false},此时的y等于undefined
console.log(a.next()) // Object{value:NaN, done:true}
var b = foo(5);
console.log(b.next()) // { value:6, done:false }
console.log(b.next(12)) // { value:8, done:false } 此时的y=2*12
console.log(b.next(13)) // { value:42, done:true } 5+24+13
Пример ввода значения в функцию Генератор через параметры следующего метода.
//例子1
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();// Started。执行了 console.log('Started');和`1. ${yield}`这两句
genObj.next('a') // 1. a。执行了 console.log(`1. ${yield}`);和`2. ${yield}`这两句
console.log(genObj.next('b') ) //2.b {value: "result", done: true}。执行了console.log(`2. ${yield}`);和return 'result';这两句
выше console.log(1. ${yield}
); Выполнить в два этапа, сначала выполнить yield, а затем выполнить console.log(), когда выполняется next();
//例子2
function* dataConsumer() {
console.log('Started');
yield 1;
yield;
var a=yield;
console.log("1. "+a);
var b=yield;
console.log("2. "+b);
return 'result';
}
let genObj = dataConsumer();
console.log( genObj.next())
console.log(genObj.next());
console.log(genObj.next('a'))
console.log( genObj.next('b'));
Выходные результаты следующие: Четыре выходных результата показаны в красной строке.
Анализ результатов: первый вызов next(), выполнение завершается на yield 1; второй вызов next() выполняется до конца yield; третий вызов next("a") выполняет yield в var a=yield четвертый вызовите метод next("b") для вызова yield в операторе var a=yield и var b=yield;17.3, для… из
Цикл for...of может автоматически перемещаться по объекту Iterator, сгенерированному при запуске функции Generator, и в это время больше нет необходимости вызывать следующий метод.
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
Как только свойство done возвращаемого объекта следующего метода становится истинным, цикл for...of прервется без возвращаемого объекта., поэтому число 6, возвращаемое оператором return приведенного выше кода, не включается в цикл for...of.
В дополнение к циклу for...of внутренне вызываются оператор расширения (...), деструктурирующее присваивание и методы Array.from, все из которых являются интерфейсами обхода. Это означает, что все они могут принимать объект Iterator, возвращаемый функцией Generator, в качестве параметра и встречать конец оператора return в функции Generator.
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循环
for (let n of numbers()) {
console.log(n)
}
// 1,2
17.4 Генератор.прототип.throw()
Выдает ошибки за пределы тела функции, а затем перехватывает их внутри тела функции-генератора.Если это глобальная команда throw(), она может быть перехвачена только оператором catch вне функции.
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');//被内部捕获,所以下面的代码还能正常运行
i.throw('b');//被外部捕获
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
Если блок кода try...catch не развернут внутри функции Generator, ошибка, вызванная методом throw, будет перехвачена внешним блоком кода try...catch.
var g = function* () {
while (true) {
yield;
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');//被外部捕获,所以下面的代码不运行了
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 外部捕获 a
Если блок кода try...catch не развернут внутри и вне функции Generator, программа сообщит об ошибке и напрямую прервет выполнение.Ошибка, вызванная методом throw, должна быть обнаружена внутри, при условии, что следующий метод должен быть выполнен хотя бы один раз.
function* gen() {
try {
yield 1;
} catch (e) {
console.log('内部捕获');
}
}
var g = gen();
g.throw(1);
// Uncaught 1
После захвата метода throw будет выполнено следующее выражение yield. То есть следующий метод будет выполнен один раз.
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
Кроме того, команда throw не имеет ничего общего с методом g.throw, и они не влияют друг на друга.
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
try {
throw new Error();
} catch (e) {
g.next();
}
// hello
// world
После того, как во время выполнения Генератора возникнет ошибка, и она не будет обнаружена внутри, она не будет выполняться снова. Если после этого будет вызван следующий метод, он вернет объект, свойство value которого равно undefined, а свойство done равно true, то есть движок JavaScript считает, что работа Генератора завершена.
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');//中断函数的运行
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第二次运行next方法', v);//因为上面代码调用时报错了,所以不会执行该语句
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第三次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
console.log('caller done');
}
log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done
17.5 Генератор.прототип.возврат()
возвращает заданное значение и завершает обход функции Генератора.
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true } //
g.next() // { value: undefined, done: true }
Если внутри функции Generator есть блок кода try...finally и блок кода try выполняется, метод возврата будет отложен до тех пор, пока блок кода finally не будет выполнен, а затем выполнен.
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
g.next() // { value: undefined, done: true }
17.6 Общие черты и различия функций Next(), Throw() и Return()
Их роль заключается в возобновлении выполнения функции Генератора и замене выражения yield другим оператором.
next() заменяет выражение yield значением.
throw() заменяет выражение yield оператором throw.
return() заменяет выражение yield оператором return.
17.7 выражения yield*
использует выражение yield* для выполнения другой функции-генератора внутри функции-генератора.
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo(); //
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x" // "a" // "b" // "y"
function* inner() {
yield 'hello!';
return "test"
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
console.log(gen.next().value) // "open"
var test=gen.next().value // 返回一个遍历器对象
console.log(test.next().value) //"hello"
console.log(test.next().value)// "test"
console.log(gen.next().value) // "close"
Функция Generator после yield* (когда нет оператора return) эквивалентна развертыванию цикла for...of внутри функции Generator.
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
// 等同于
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
Если за yield* следует массив, поскольку массивы изначально поддерживают обходчики, будут пройдены члены массива.
function* gen(){
yield* ["a", "b", "c"];
}
console.log(gen().next()) // { value:"a", done:false }
На самом деле, yield* может пройти через любую структуру данных, если она имеет интерфейс Iterator.Если делегированная функция-генератор имеет оператор return, она может возвращать данные в делегированную функцию-генератор.
function* foo() {
yield 2;
yield 3;
return "foo";
}
function* bar() {
yield 1;
var v = yield* foo();
console.log("v: " + v);
yield 4;
}
var it = bar();
it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
for(let x of iterTree(tree)) {
console.log(x);
}
// a // b // c // d // e
17.8 Генераторная функция как свойство объекта
let obj = {
* myGeneratorMethod() {
•••
}
};
17.9, это Генераторная функция
Функция генератора всегда возвращает обходчик.ES6 указывает, что этот обходчик является экземпляром функции генератора, а также наследует методы объекта-прототипа функции генератора..
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
Генерируя пустой объект, используйте метод call, чтобы связать его внутри функции Generator.
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};
var f = F.call(obj);//调动F()并且把obj作为this传进去,这样给obj添加a、b、c属性
console.log(f.next()); // Object {value: 2, done: false}
console.log(f.next()); // Object {value: 3, done: false}
console.log(f.next()); // Object {value: undefined, done: true}
console.log(obj.a) // 1
console.log(obj.b) // 2
console.log(obj.c) // 3
Замените obj на F.prototype. Объедините эти два объекта. Затем измените F на конструктор, и вы сможете выполнить над ним новую команду.
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
Несколько потоков (в случае одного потока, то есть несколько функций) могут выполняться параллельно, но только один поток (или функция) находится в состоянии выполнения, другие потоки (или функции) находятся в приостановленном состоянии (приостановлено). ), поток (или функция) ) может обмениваться правами на выполнение. Потоки (или функции), которые выполняются параллельно и обмениваются правами на выполнение, называются сопрограммами.
17.10, Заявка
1. Синхронное выражение асинхронной операции. Развертывание операций Ajax с помощью функций генератора может быть выражено синхронным образом.
function makeAjaxCall(url,callBack){
var xhr;
if (window.XMLHttpRequest)
{
//IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xhr=new XMLHttpRequest();
}else{
// IE6, IE5 浏览器执行代码
xhr=new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open("GET",makeAjaxCall,true);//确保浏览器兼容性。
xhr.onreadystatechange=function(){
if (xhr.readyState==4 && xhr.status==200)
{
if(xhr.status>=200&&xhr.status<300||xhr.status==304){
callBack(xhr.responseText;);
}
}
}
xmlhttp.send();
}
function* main() {
var result = yield request("https://juejin.im/editor/posts/5cb209e36fb9a068b52fb360");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);//将response作为上一次yield的返回值
});
}
var it = main();
it.next();
Файл можно прочитать вручную построчно, используя выражение yield.
function* numbers() {
let file = new FileReader("numbers.txt");
try {
while(!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
}
2, управление потоком управления
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
Использование промисов
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
Использование генератора
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
function step1(value){
return value*2;
}
function step2(value){
return value*2;
}
function step3(value){
return value*2;
}
function step4(value){
return value*2;
}
Обратите внимание, что описанный выше подход подходит только для синхронных операций, то есть все задачи должны быть синхронными и не могут иметь асинхронных операций. 3, развернуть интерфейс итератора
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
4, как структура данных
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {}
// task是一个函数,可以像回调函数那样使用它
17.11 Асинхронный вызов функции Генератора (**Нужно понять и понять**)
Основные методы асинхронного программирования следующие:1. Функция обратного вызова (слишком сильная связь)
2. Мониторинг событий
3. Опубликовать/подписаться
4. Объект обещания
5. генератор
1. Используйте генератор для инкапсуляции асинхронных функций
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
Сначала выполните функцию Generator, получите объект обходчика, а затем используйте следующий метод (вторая строка) для выполнения первой фазы асинхронной задачи. Поскольку модуль Fetch возвращает объект Promise, используйте метод then для вызова следующего метода next.
2. Функция Thunk
Реализация "вызова по имени" компилятора обычно помещает параметры во временную функцию, а затем передает временную функцию в тело функции. Эта временная функция называется функцией Thunk.
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
f(thunk)
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback);
};
};
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
3. Автоматическое выполнение на основе объекта Promise
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
Затем вручную запустите функцию Генератора выше.
var g = gen();
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
});
автоматическая запись исполнителя:
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
run(gen);
18. асинхронная функция
Асинхронная функция — это синтаксический сахар для функции Генератора. Асинхронная функция состоит в том, чтобы заменить звездочку (*) функции генератора на асинхронную, а доход заменить на ожидание., это все. Улучшение асинхронной функции по сравнению с функцией генератора отражено в следующих четырех пунктах.
- Встроенный привод.Вызывается функция asyncReadFile, затем она выполняется автоматически и выводит окончательный результат. То есть выполнение асинхронной функции точно такое же, как и у обычной функции, только одна строка.
- лучшая семантика.async означает, что в функции есть асинхронная операция, а await означает, что следующему выражению необходимо дождаться результата.
- более широкая применимость.После команды await это может быть объект Promise и значение примитивного типа (числовое, строковое и логическое, но оно будет автоматически преобразовано в немедленно разрешенный объект Promise).
- Возвращаемое значение — обещание.Возвращаемое значение асинхронной функции — это объект Promise, далееАсинхронную функцию можно рассматривать как несколько асинхронных операций, упакованных в объект Promise, а команда await — это синтаксический сахар внутренней команды then..
18.1, Асинхронный синтаксис
1. Асинхронная функция возвращает объект Promise.
Значение, возвращаемое оператором return внутри асинхронной функции, станет параметром функции обратного вызова метода then. Внутри асинхронной функции возникает ошибка, из-за которой возвращаемый объект Promise переходит в состояние отклонения. Выброшенный объект ошибки будет получен функцией обратного вызова метода catch.
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
async function f() {
throw new Error('出错了');
}
f().then(
v => console.log(v),
e => console.log(e)
)
2. Состояние объекта Promise меняется.
Объект Promise, возвращаемый функцией async, должен ждать, пока все объекты Promise, следующие за командой await, не будут выполнены до изменения состояния, если только не встречается оператор return или не возникает ошибка. То есть функция обратного вызова, указанная в методе then, будет выполнена только после выполнения асинхронной операции внутри асинхронной функции.
18.2, Ожидание команды
Обычно за командой await следует объект Promise, который возвращает результат объекта. Если это не объект Promise, он напрямую возвращает соответствующее значение.
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
В другом случае за командой await следует объект thenable (то есть объект, определяющий метод then), тогда await будет эквивалентен объекту Promise.
18.3, Обработка ошибок
Если в асинхронной операции после await возникает ошибка, объект Promise, эквивалентный объекту, возвращаемому асинхронной функцией, отклоняется.
async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
18.4 Меры предосторожности при использовании
1) Объект обещания после того, как команда ожидания может быть отклонена, поэтому лучше поставить команду await в попытке ... Catch Code Block.
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
2) Для асинхронных операций за несколькими командами await, если нет вторичной связи, лучше всего позволить им запускаться в одно и то же время.
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;//直接返回
let bar = await barPromise;
3) Команду await можно использовать только в асинхронных функциях, при использовании в обычных функциях будет выдано сообщение об ошибке.
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
Если вы действительно хотите, чтобы несколько запросов выполнялись одновременно, вы можете использовать метод Promise.all.
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
4) Асинхронные функции могут поддерживать работающий стек.
const a = () => {
b().then(() => c());
};
Когда b() работает, функция a() не будет прервана, а будет продолжать выполняться. Когда b() завершает работу, возможно, a() уже завершил работу, и контекст, в котором находится b(), исчез. Если b() или c() сообщают об ошибке, стек ошибок не будет включать a().
const a = async () => {
await b();
c();
};
Когда запускается b(), a() приостанавливает выполнение, и контекст сохраняется. Как только b() или c() сообщат об ошибке, стек ошибок будет включать a().
18.5 Пример: последовательное выполнение асинхронных операций
async function logInOrder(urls) {
for (const url of urls) {
const response = await fetch(url);
console.log(await response.text());
}
}
Проблема с приведенным выше кодом заключается в том, что все удаленные операции вторичны. Следующий URL-адрес будет прочитан только в том случае, если предыдущий URL-адрес возвращает результат, что неэффективно и является пустой тратой времени.
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
Хотя параметр метода map является асинхронной функцией, он выполняется одновременно, потому что последовательно выполняется только внутренняя часть асинхронной функции, а внешняя не затрагивается.
18.6, Асинхронный обходчик
Самая большая грамматическая особенность асинхронного обходчика заключается в том, что вызывается следующий метод обходчика, который возвращает объект Promise.
asyncIterator
.next()
.then(
({ value, done }) => /* ... */
);
18.7 Функция асинхронного генератора
Синтаксически асинхронная функция-генератор представляет собой комбинацию асинхронной функции и функции-генератора.
async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
Внутри функции асинхронного генератора команды await и yield могут использоваться одновременно. Таким образом можно понять, что команда await используется для ввода значения, сгенерированного внешней операцией, в функцию, а команда yield используется для вывода значения внутри функции.
19. Класс
19.1, основной синтаксис класса
Сочинение нового класса простоСделать написание прототипов объектов более понятным и похожим на синтаксис объектно-ориентированного программирования.Вот и все. Классы ES6 можно рассматривать как другой способ написания конструкторов. Фактически,Все методы класса определяются в свойстве прототипа класса.
1. Классы ES6 можно рассматривать как еще один способ написания конструкторов. Сам класс указывает на конструктор.
Point === Point.prototype.constructor // true
2. Все методы класса определяются в свойстве прототипа класса.
3. Вызов метода для экземпляра класса фактически вызывает метод для прототипа.
p1.constructor === Point.prototype.constructor // true
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
//改成类的写法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true 类本身就指向构造函数。
var p1=new Point(2,4);
p1.constructor === Point.prototype.constructor // true
Point.prototype.constructor === Point // true
Object.keys(Point.prototype)// []
В приведенном выше коде метод toString — это метод, определенный внутри класса Point, который не является перечисляемым. Это несовместимо с поведением ES5.
19.1, метод конструктора
Метод конструктора возвращает объект экземпляра (то есть это) по умолчанию, и вы можете указать, чтобы вернуть другой объект. Класс должен быть вызван новым, в противном случае ошибка будет сообщена.
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
19.2, экземпляр класса
Как и в ES5, свойства экземпляра определяются в прототипе (то есть в классе), если только они не определены явно в самом себе (то есть в объекте this).
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
//toString是原型上的方法,构造方法中的才是实例属性
Как и в ES5, все экземпляры класса имеют общий объект-прототип.
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__
//true
19.3, функция значения (геттер) и функция значения (сеттер)
Ключевые слова get и set можно использовать внутри «класса», чтобы установить функцию сохранения значения и функцию значения для определенного атрибута, а также перехватить поведение доступа к атрибуту.
19.4, выражения атрибутов
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
19.5, Выражение класса
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
Название этого класса меня, я, но только в классе доступен, обратитесь к текущему классу. В своем классе на улице этот класс ссылается только на MyClass.
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
Если внутренняя часть класса не используется, вы можете опустить Me.
const MyClass = class { /* ... */ };
Используя выражение класса, вы можете написать класс, который выполняется немедленно.
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
Примечания для класса:
1.Строгий режим. Внутри классов и модулей по умолчанию используется строгий режим.
2. Акции нет. Для классов нет подъема переменных.
3. Атрибут name всегда возвращает имя класса сразу после ключевого слова класса.
4, Генераторный метод. Метод класса Symbol.iterator Foo возвращает ходок по умолчанию, поскольку... цикла автоматически вызовет этот ходок.
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x); // hello,world
}
5.Смысл этого.Если метод класса содержит это, по умолчанию он является экземпляром класса.Однако следует соблюдать большую осторожность, так как этот метод может сообщать об ошибках, если он используется отдельно. это будет указывать на среду, в которой работает метод (поскольку класс находится в строгом режиме, так что на самом деле это указывает на undefined)
class Logger {
printName(name = 'there') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName(); // TypeError: Cannot read property 'print' of undefined 本来是实例的方法,但是此时printName()不是实例调用的,所以this指向不明,默认为undefined
Более простое решение — связать this в конструкторе, чтобы метод печати не был найден.
class Logger {
constructor() {
this.printName = this.printName.bind(this);
}
// ...
}
19.6, статические методы
Если ключевое слово static добавлено перед методом, это означает, что метод не будет наследоваться экземпляром, а будет вызываться непосредственно через класс, который называется «статическим методом». Если статический метод содержит ключевое слово this, this относится к классу, а не к экземпляру. Статические методы могут иметь то же имя, что и нестатические методы. class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
Статический метод родительского класса может быть унаследован дочерним классом.
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
Статические методы также можно вызывать из суперобъектов.
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
19.7, новый метод записи атрибута силы
Это свойство также можно определить на верхнем уровне класса, а остальные останутся без изменений. Преимущество этого нового способа написания состоит в том, что все атрибуты самого объекта экземпляра определены в заголовке класса, что выглядит аккуратно, и вы можете с первого взгляда увидеть, какие атрибуты экземпляра имеет этот класс.
class IncreasingCounter {
_count = 0;
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}
19.8 Статические свойства
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp); // 42
}
}
19.9 Частные методы и частные свойства
1. Переместите частные методы из модуля, потому что все методы внутри модуля видны внешнему миру.
class Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
function bar(baz) {
return this.snaf = baz;
}
2. Используйте уникальность значения Symbol, чтобы назвать закрытый метод значением Symbol. В обычных условиях их нельзя получить, поэтому достигается эффект приватных методов и приватных свойств. Но это не совсем невозможно, Reflect.ownKeys() все еще может их получить.
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
// ...
};
19.10, новая.цель()
ES6 вводит свойство new.target для новой команды, которое обычно используется в конструкторах,Возвращает конструктор, на который действует новая команда. Если конструктор не вызывается с помощью новой команды или Reflect.construct(), new.target вернет неопределенное значение, поэтомуЭто свойство можно использовать для определения того, как вызывается конструктор.Класс вызывает внутри себя new.target и возвращает текущий класс. Вне функции использование new.target вызовет ошибку.
function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
Когда подкласс наследует родительский класс, new.target вернет подкласс. В основном, чтобы увидеть, какой класс стоит за новыми
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
// ...
}
}
class Square extends Rectangle {
constructor(length,width) {
super(length, width);
}
}
var c=new Rectangle(1,2);
var obj = new Square(3); // 输出 false
19.11 Наследование классов
Class может реализовать наследование через ключевое слово extends, что намного понятнее и удобнее, чем реализация наследования в ES5 путем изменения цепочки прототипов. class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
1. Ключевое слово super, которое здесь представляет конструктор родительского класса, используется для создания объекта this родительского класса.
2. Подкласс должен вызывать метод super в методе конструктора, иначе при создании нового экземпляра будет сообщено об ошибке. Это связано с тем, что объект this подкласса должен сначала быть сформирован конструктором родительского класса, чтобы получить те же атрибуты и методы экземпляра, что и родительский класс, а затем обработать его, а также собственные атрибуты и методы экземпляра подкласса.Если вы не вызовете метод super, подклассы не получат этот объект. Или, если вы не пишете конструктор(){}, вы должны написать супер().
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
————————————————————————————————————————————————————————————
class ColorPoint extends Point {
}
// 等同于
class ColorPoint extends Point {
constructor(...args) {
super(...args);
}
}
3, наследование ES5, по сути, заключается в создании экземпляра объекта этого подкласса, а затем добавлении к этому родительскому классу выше (Parent.apply(this)). Механизм наследования в ES6 совершенно другой, по сути, сначала свойства и методы экземпляра объекта родительского класса, это было добавлено к вышеперечисленному (необходимо вызывать суперметоды), затем с помощью конструктора модификации этого подкласса.
4. В конструкторе подкласса ключевое слово this можно использовать только после вызова super, иначе будет сообщено об ошибке. Это связано с тем, что конструкция экземпляра подкласса основана на экземпляре родительского класса, и только суперметод может вызывать экземпляр родительского класса. 5 Объект экземпляра подкласса cp является экземпляром как ColorPoint, так и Point (родительский класс), что в точности совпадает с поведением ES5.
6 Статический метод родительского класса также будет унаследован дочерним классом.
19.12. Object.getPrototypeOf()
Метод Object.getPrototypeOf можно использовать для получения родительского класса из дочернего класса. Вы можете использовать этот метод, чтобы определить, наследуется ли класс от другого класса. Object.getPrototypeOf(ColorPoint) === Point// true
19.13, Суперключевое слово
1,Когда super вызывается как функция, она представляет конструктор родительского класса.. ES6 требует, чтобы конструктор подкласса выполнял суперфункцию один раз. Хотя super представляет конструктор родительского класса A, он возвращает экземпляр подкласса B.При использовании в качестве функции super() можно использовать только в конструкторе подкласса, а в других местах будет сообщено об ошибке.
class A {
constructor() {
console.log(new.target.name);//new.targe构造函数
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
2. Когда super используется как объект, в обычных методах он указывает на объект-прототип родительского класса, в статических методах он указывает на родительский класс. Поэтому методы или свойства, определенные в экземпляре родительского класса, нельзя вызывать через super.
lass A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
При вызове метода суперкласса через super в обычном методе подкласса this внутри метода указывает на текущий экземпляр подкласса.
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m() // 2
Поскольку this указывает на экземпляр подкласса, если вы присваиваете свойство свойству через super, тогда super является this, и присвоенное свойство становится свойством экземпляра подкласса.
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;//此时的super相当于this
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
При чтении super.x читается A.prototype.x, поэтому возвращается значение undefined.
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
В статическом методе B.m super.print указывает на статический метод родительского класса. this в этом методе указывает на B, а не на экземпляр B.
19.14, свойство прототипа и свойство __proto__ класса
В реализации ES5 каждый объект имеет свойство __proto__, которое указывает на свойство прототипа соответствующего конструктора.
instance.__proto__===A.prototype//instance是A的实例
Класс, как синтаксический сахар конструктора, имеет как атрибут прототипа, так и атрибут __proto__, так что цепочек наследования две одновременно.
(1) Атрибут __proto__ подкласса,представляет наследование конструкторов,Всегда обращайтесь к родительскому классу.
(2) Атрибут __proto__ атрибута прототипа подкласса, ** указывает на наследование метода, а ** всегда указывает на атрибут прототипа родительского класса.
class A {
}
class B extends A {
}
console.log(B.__proto__ === A) // true,
console.log(B.prototype.__proto__ === A.prototype )// true,
// 等同于
Object.create(A.prototype);
В качестве объекта прототип (свойство __proto__) подкласса (B) является родительским классом (A); в качестве конструктора объект-прототип (свойство прототипа) подкласса (B) является объектом-прототипом (свойство прототипа) класса экземпляр родительского класса.
19.15, атрибут __proto__ экземпляра
Свойство __proto__ свойства __proto__ экземпляра подкласса указывает на свойство __proto__ экземпляра родительского класса. То есть прототип прототипа подкласса является прототипом родительского класса. (p2 — дочерний класс, p1 — родительский класс)
p2.__proto__.__proto__ === p1.__proto__ // true
解析:
p2.__proto__===p2的类.prototype;
p2的类.prototype.__proto__===p2的类的父类的.prototype
p1.__proto__===p2的类的父类的.prototype。
Таким образом, поведение экземпляра родительского класса можно изменить с помощью свойства __proto__.__proto__ экземпляра дочернего класса.
p2.__proto__.__proto__.printName = function () {
console.log('Ha');
};
p1.printName() // "Ha"
20. Модуль
20, 1 строгий режим
Модули ES6 автоматически используют строгий режим, независимо от того, добавили ли вы «использовать строгий режим» в заголовок модуля. Строгий режим в основном имеет следующие ограничения.
- Переменные должны быть объявлены перед использованием.
- Параметры функции не могут иметь атрибуты с одинаковыми именами, иначе будет сообщено об ошибке.
- Оператор with нельзя использовать.
- Нельзя присваивать значения свойствам только для чтения, иначе будет сообщено об ошибке.
- Вы не можете использовать префикс 0 для представления восьмеричного числа, иначе будет сообщено об ошибке.
- Невозможно удалить нечитаемые свойства, иначе сообщается об ошибке.
- Переменная delete prop не может быть удалена, будет сообщено об ошибке, может быть удален только атрибут delete global[prop].
- eval не вводит переменные во внешнюю область видимости (не понимаю).
- eval и аргументы не могут быть переназначены.
- arguments не отражает автоматически изменения аргументов функции.
- arguments.callee нельзя использовать. (указывает на функцию, используемую для объекта arguments)
- Нельзя использовать Arguments.caller, значение не определено. (Свойство caller содержит ссылку на функцию, вызвавшую текущую функцию)
- Запретить это указывать на глобальный объект.
- Вы не можете использовать fn.caller и fn.arguments для получения стека вызовов функций.
- Добавлены зарезервированные слова (такие как protected, static и interface).
20.2 Использование экспорта
Команда export используется дляЗадает внешний интерфейс модуля, команда импорта используется для импорта функций, предоставляемых другими модулями. Экспорт формулировки категории:
1 фигурные скобки указывают набор переменных, которые необходимо вывести. экспорт {имя, фамилия, год}; 2, прямой вывод переменной с использованием ключевого слова экспорта. год экспорта var = 1958;
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
等同于下面这中写法
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
Обычно переменная, выводимая при экспорте, имеет исходное имя, но ее можно переименовать с помощью ключевого слова as.
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
Примечание 1: Команда экспорта указывает внешний интерфейс и должна установить однозначное соответствие с переменными внутри модуля.
// 报错
export 1;
// 报错
var m = 1;
export m;
// 报错
function f() {}
export f;
Примечание 2. Интерфейс, выводимый оператором экспорта,Его соответствующее значение представляет собой отношение динамической привязки., то есть через этот интерфейс можно получить значение в реальном времени внутри модуля.
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
Примечание 3. Команда экспорта может отображаться в любом месте модуля, если она находится на верхнем уровне модуля.
function foo() {
export default 'bar' // SyntaxError
}
foo()
20. Использование 3 импорта
Все переменные, вводимые командой import, доступны только для чтения, поскольку по сути это интерфейс ввода. То есть нельзя переписывать интерфейс в скрипте, загружающем модуль. import {a} from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
但是,如果a是一个对象,改写a的属性是允许的。
import {a} from './xxx.js'
a.foo = 'hello'; // 合法操作
From после import указывает расположение файла модуля, которое может быть относительным или абсолютным путем, а суффикс .js можно опустить. Если это просто имя модуля без пути, то должен быть файл конфигурации, который сообщает движку JavaScript, где находится модуль.
import {myMethod} from 'util';
//util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。
Обратите внимание, что команда импорта имеет эффект подъема, который будет поднят на голову всего модуля и выполнен первым.импорт выполняется статически, поэтому нельзя использовать выражения и переменные, это синтаксические конструкции, которые получают результаты только во время выполнения.
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
Укажите методы загрузки по одному:
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
20, 4 Общая загрузка модуля импорта *
Запись для общей загрузки: импорт * из "модуля"
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
20、5 экспорт по умолчанию
Используйте команду экспорта по умолчанию, чтобы указать вывод по умолчанию для модуля. // export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
//因为是默认输出的,所以这时import命令后面,不使用大括号。并且可以随意取名。
customName(); // 'foo'
1. В следующем коде имя функции foo функции foo недействительно вне модуля. При загрузке загружается как анонимная функция.
function foo() {
console.log('foo');
}
export default foo;
2. Модуль может иметь только один выход по умолчанию, поэтому команду экспорта по умолчанию можно использовать только один раз. Поэтому фигурные скобки после команды импорта не нужны, потому что можно только соответствовать команде экспорта по умолчанию.По сути, экспорт по умолчанию экспортирует переменную или метод, называемый по умолчанию, и система позволяет вам давать ему имя. Однако рекомендуется использовать имя по умолчанию при импорте.
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
3. Поскольку суть команды экспорта по умолчанию заключается в присвоении следующего значения переменной по умолчанию, вы можете напрямую записать значение после экспорта по умолчанию.
// 正确
export default 42;
// 报错
export 42;
4.Если вы хотите ввести метод по умолчанию (по умолчанию) и другие интерфейсы одновременно в операторе импорта, вы можете написать это следующим образом.
import _, { each, forEach } from 'lodash';
5, экспорт по умолчанию также можно использовать для экспорта классов.
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();
20, 5 Составное написание экспорта и импорта
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
После того, как записано в одну строку, foo и bar на самом деле не импортируются в текущий модуль, но эквивалентны пробросу этих двух интерфейсов во внешний мир, так что текущий модуль не может напрямую использовать foo и bar. Интерфейс по умолчанию написан следующим образом.
export { default } from 'foo';
Сменить именованный интерфейс на интерфейс по умолчанию можно следующим образом.
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
Аналогичным образом, интерфейс по умолчанию также может быть переименован с именованным интерфейсом.
export { default as es6 } from './someModule';
20, 6 Наследование модулей
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
Экспорт* в приведенном выше коде означает экспорт всех свойств и методов модуля Circle. *Обратите внимание, что экспортКоманда игнорирует метод модуля круга по умолчанию.
// main.js
import * as math from 'circleplus';//整体加载的写法
import exp from 'circleplus';
console.log(exp(math.e));
import exp表示,将circleplus模块的默认方法加载为exp方法。
20, 7 Импорт()
может реализовать динамическую нагрузку. Выполнение во время выполнения, то есть при выполнении этого предложения будет загружен указанный модуль.import() возвращает объект Promise.
Примечание: после того, как import() успешно загрузит модуль, этот модуль будет использоваться как объект в качестве параметра метода then. Следовательно, выходной интерфейс можно получить, используя синтаксис назначения деструктуризации объекта.
import('./myModule.js')
.then(({export1, export2}) => {
// ...•
});
В приведенном выше коде экспорт1 и экспорт2 являются выходными интерфейсами myModule.js, которые можно получить путем деконструкции. Если модуль имеет выходной интерфейс по умолчанию, его можно получить напрямую с параметрами.
import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});
Приведенный выше код также может использовать форму именованного ввода.
import('./myModule.js')
.then(({default: theDefault}) => {
console.log(theDefault);
});
20, 8 реализация загрузки модулей
Браузер загружает модули ES6, также используя тег script, но добавляя атрибут type="module". <script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>
Для сценариев внешних модулей (foo.js в приведенном выше примере) следует отметить несколько моментов.
1. Код выполняется в области модуля, а не в глобальной области. Переменные верхнего уровня внутри модуля, не видимые снаружи.
2.Скрипты модулей автоматически используют строгий режим, независимо от того, объявлено ли использование строгим.
3. Среди модулей вы можете использовать команду импорта для загрузки других модулей (суффикс .js нельзя опускать, и необходимо указать абсолютный URL-адрес или относительный URL-адрес), или вы можете использовать команду экспорта для вывода внешнего интерфейса.
4. В модулях ключевое слово this на верхнем уровне возвращает undefined вместо указания на окно. То есть использование ключевого слова this на верхнем уровне модуля бессмысленно.
5. Если один и тот же модуль загружен несколько раз, он будет выполнен только один раз.
Используя синтаксическую точку верхнего уровня this equals undefined, вы можете определить, находится ли текущий код в модуле ES6.
const isNotModuleScript = this !== undefined;
20, 9 модулей ES6 и модулей CommonJS
Модули ES6 полностью отличаются от модулей CommonJS. У них есть два основных отличия.1. Вывод модуля CommonJS — это копия значения, а вывод модуля ES6 — ссылка на значение.
2. Модули CommonJS загружаются во время выполнения. , модули ES6 являются выходными интерфейсами времени компиляции. .
Второе отличие заключается в том, что CommonJS загружаетявляется объектом (т. е. свойством module.exports), объект будет сгенерирован только после завершения работы скрипта. И модуль ES6 не является объектом, его внешний интерфейс простостатическое определение, который будет создан на этапе статического анализа кода.
Первое отличие заключается в том, что модули CommonJS выводят копию значения, то есть после вывода значения изменения внутри модуля не повлияют на значение. Модули ES6 динамически ссылаются и не кэшируют значения.Переменные в модулях привязаны к модулю, в котором они расположены.
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
Это связано с тем, что mod.counter является примитивным значением и будет кэшироваться.. Если это не написано как функция, значение после внутреннего изменения может быть получено.
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 4
Вы можете добавить свойства в obj, но переназначение сообщит об ошибке.Поскольку адрес, на который указывает переменная obj, доступен только для чтения и не может быть переназначен, похоже, что main.js создал константную переменную с именем obj.
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
Разница между внутренними переменными commonJS и ES6:
1. В модулях ES6 верхний уровень this указывает на undefined, верхний уровень this в модулях CommonJS указывает на текущий модуль.
2. Следующие переменные верхнего уровня не существуют в модулях ES6.
- arguments
- require
- module
- exports
- __filename
- __dirname
20.10, ES6 загружает модуль CommonJS (общий ввод)
Node автоматически использует свойство module.exports в качестве вывода модуля по умолчанию, что эквивалентно экспорту по умолчанию xxx. // a.js
module.exports = {
foo: 'hello',
bar: 'world'
};
// 等同于
export default {
foo: 'hello',
bar: 'world'
};
Поскольку модуль ES6 определяет выходной интерфейс, модуль определения выходных интерфейсов Commonjs Runtime, при использовании модуля загрузочного модуля Commonjs Sommitjs, как разрешено следующее время компиляции.
// 不正确
import { readFile } from 'fs';
Поскольку fs находится в формате CommonJS, интерфейс readFile может быть определен только во время выполнения, а команда импорта требует, чтобы этот интерфейс был определен во время компиляции.Решение состоит в том, чтобы перейти на общий ввод.
// 正确的写法一
import * as express from 'express';
const app = express.default();
// 正确的写法二
import express from 'express';
const app = express();
20.11, CommonJS загружает модули ES6 (функция import())
Модули CommonJS загружают модули ES6, вы не можете использовать команду require, но используйте функцию import(). Все выходные интерфейсы модулей ES6 станут свойствами входного объекта.20.12 Принцип загрузки модулей CommonJS.
При первой загрузке сценария команда require выполняет весь сценарий и создает объект в памяти. {
id: '...',
exports: { ... },
loaded: true,
...
}
Атрибут ID объекта - это имя модуля, экспорт являются свойством каждого вывода интерфейсных модулей, загруженный атрибут - это логическое значение, которое указывает, завершен ли скрипт модуля. Здесь пропускается много других атрибутов. После необходимости использовать этот модуль, и они будут превосходить значение экспорта свойства. Даже если вы снова выполняете команду, оно не будет выполнять модуль еще раз, но в значение кэша.То есть независимо от того, сколько раз загружается модуль CommonJS, он будет запускаться только один раз при первой загрузке, а при последующей загрузке он вернет результат первого запуска, если только система не кеш очищается вручную.
20.13, циклическая загрузка CommonJS
Как только модуль "циклически загружается", будет выводиться только та его часть, которая была выполнена, а та часть, которая не была выполнена, не будет выводиться. //a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
//main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
$ node main.js
Результат выполнения следующий:
Подробный процесс выполнения в main.js выглядит следующим образом:
Скрипт a.js сначала выводит переменную done, а затем загружает другой файл скрипта b.js.Обратите внимание, что в это время код a.js останавливается, ожидая завершения выполнения b.js, а затем выполняется вниз.Когда b.js выполняется до второй строки, он загружает a.js, в это время происходит «циклическая загрузка». Система перейдет к атрибуту экспорта соответствующего объекта модуля a.js, чтобы получить значение, но поскольку a.js не был выполнен, из атрибута экспорта можно получить только выполненную часть, а не окончательное значение. (Выполненная часть a.js — это только одна строка.) Затем b.js продолжает выполняться, а затем возвращает право на выполнение a.js после завершения всех исполнений. Поэтому a.js продолжает выполняться до тех пор, пока выполнение не будет завершено.
20.14, циклическая загрузка модулей ES6
Модули ES6 являются динамическими ссылками, если вы используете импорт для загрузки переменных из модуля (т.е. импортируете foo из 'foo'), эти переменные не будут кэшироваться, а станут ссылкой на загруженный модуль
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
//function foo() { return 'foo' }
//export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';
//function bar() { return 'bar' }
//export {bar};
$ node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined
Подробный процесс выполнения приведенного выше кода выглядит следующим образом:
Во-первых, после выполнения a.mjs движок обнаруживает, что он загрузил b.mjs, поэтому он сначала выполнит b.mjs, а затем выполнит a.mjs. Затем, когда b.mjs выполняется, известно, что он получил входной интерфейс foo от a.mjs.В это время он не будет выполнять a.mjs, но будет думать, что этот интерфейс уже существует, и продолжит выполнение. При выполнении третьей строки console.log(foo) обнаруживается, что этот интерфейс вообще не определен, поэтому сообщается об ошибке. Это можно решить, написав foo как функцию. Это связано с тем, что функция имеет эффект подъема (подъем наверх).При выполнении импорта {bar} из './b' функция foo уже определена, поэтому b.mjs не сообщит об ошибке при загрузке. Это также означает, что если функция foo будет переписана как функциональное выражение, также будет сообщено об ошибке.
21. Стиль программирования (оптимизация производительности)
- Рекомендуется больше не использовать команду var, а вместо этого использовать команду let.
- Между let и const предпочтительно использовать const, особенно в глобальной среде, переменные не должны устанавливаться, должны устанавливаться только константы. Причины: одна в том, что const может напоминать людям, читающим программу, что эту переменную не следует изменять; другая в том, что const больше соответствует идее функционального программирования, операция не изменяет значение, а только создает новое значение, и это также способствует будущим распределенным операциям, наконец, одна из причин заключается в том, что компилятор JavaScript оптимизирует const, поэтому использование const more полезно для повышения эффективности работы программы. let и const на самом деле являются разницей во внутренней обработке компилятора.
- В статических строках всегда используются одинарные или обратные кавычки, а не двойные кавычки. Динамические строки используют обратные кавычки.
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// good
const a = 'foobar';
const b = `foo${a}bar`;
- присваивание деструктуризации При присвоении значений переменным с использованием членов массива предпочтительны деструктурирующие присваивания.
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
Если параметр функции является членом объекта, предпочтение отдается деструктурирующему присваиванию.
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {
const { firstName, lastName } = obj;
}
// best
function getFullName({ firstName, lastName }) {
}
- объект
Объект определяется одной строкой, последний элемент не заканчивается запятой. Объекты, определенные в нескольких строках, последний элемент которых заканчивается запятой.
// bad
const a = { k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};
// good
const a = { k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
Объект должен быть как можно более статичным. После определения нельзя добавлять новые свойства по желанию. Если добавление свойств неизбежно, используйте метод Object.assign.
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
- Используйте оператор распространения (...), чтобы скопировать массив. Преобразуйте объект, похожий на массив, в массив с помощью метода Array.from.
const itemsCopy = [...items];
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
- Рекомендуются простые, однострочные, одноразовые функции, стрелочные функции. Если тело функции более сложное и количество строк велико, следует использовать традиционный метод написания функции.
- Не используйте переменную arguments внутри тела функции, вместо этого используйте оператор rest (...).
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
- Используйте синтаксис значения по умолчанию, чтобы установить значения по умолчанию для параметров функции.
// bad
function handleThings(opts) {
opts = opts || {};
}
// good
function handleThings(opts = {}) {
// ...
}
- Обратите внимание на различие между Объектом и Картой.Объект используется только при моделировании объектов сущностей реального мира. Если вам просто нужна структура данных ключ: значение, используйте структуру Map. Потому что Map имеет встроенный механизм обхода.
- Всегда используйте класс вместо операций, требующих прототипа. Потому что Class написан лаконичнее и понятнее.
// bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
// good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}
- Используйте расширения для реализации наследования, потому что это проще и не рискует нарушить операцию instanceof.
- Если модуль имеет только одно выходное значение, используется экспорт по умолчанию, а если модуль имеет несколько выходных значений, экспорт по умолчанию не используется. Не используйте экспорт по умолчанию и обычный экспорт одновременно.
- Не используйте подстановочные знаки во входных данных модуля. Потому что это гарантирует, что ваш модуль имеет экспорт по умолчанию.
// bad
import * as myObject from './importModule';
// good
import myObject from './importModule';
- Если модуль экспортирует функцию по умолчанию, первая буква имени функции должна быть строчной. Если модуль экспортирует объект по умолчанию, первая буква имени объекта должна быть заглавной.
function makeStyleGuide() {
}
export default makeStyleGuide;//函数
const StyleGuide = {
es6: {
}
};
export default StyleGuide;//对象