资讯专栏INFORMATION COLUMN

每个JavaScript开发人员都应该知道的新ES2018功能(译文)

leonardofed / 1160人阅读

摘要:为了使程序员能够一次一个地处理集合中的元素,引入了迭代器接口。迭代器使用该方法获取对象属性名称的数组,然后将其分配给常量。迭代器的缺点是它们不适合表示异步数据源。每次循环时,都会调用迭代器的方法,它返回一个。

前言

原文地址:https://css-tricks.com/new-es2018-features-every-javascript-developer-should-know/

原文作者:Faraz Kelhini

译者:Timbok

翻译工具:Google Translate

本文首发于我的个人网站: Timbok.top
正文

ECMAScript标准的第九版,官方称为ECMAScript 2018(或简称ES2018),于2018年6月发布。从ES2016开始,ECMAScript规范的新版本每年发布而不是每几年发布一次,并且添加的功能少于主要版本以前。该标准的最新版本通过添加四个新RegExp功能,rest/spread属性,asynchronous iteration,和Promise.prototype.finally。此外,ES2018从标记模板中删除了转义序列的语法限制。

这些新变化将在后面的小节中解释。

rest/spread属性

ES2015最有趣的功能之一是点差运算符。该运算符使复制和合并数组变得更加简单。您可以使用运算符...,而不是调用concat()or slice()方法:

const arr1 = [10, 20, 30];

// make a copy of arr1
const copy = [...arr1];

console.log(copy);    // → [10, 20, 30]

const arr2 = [40, 50];

// merge arr2 with arr1
const merge = [...arr1, ...arr2];

console.log(merge);    // → [10, 20, 30, 40, 50]

在必须作为函数的多带带参数传入数组的情况下,扩展运算符也派上用场。例如:

const arr = [10, 20, 30]

// equivalent to
// console.log(Math.max(10, 20, 30));
console.log(Math.max(...arr));    // → 30

ES2018通过向对象文字添加扩展属性来进一步扩展此语法。使用spread属性,您可以将对象的自身可枚举属性复制到新对象上。请考虑以下示例:

const obj1 = {
  a: 10,
  b: 20
};

const obj2 = {
  ...obj1,
  c: 30
};

console.log(obj2);    // → {a: 10, b: 20, c: 30}

在此代码中,...运算符用于检索属性obj1并将其分配给obj2。在ES2018之前,尝试这样做会引发错误。如果有多个具有相同名称的属性,则将使用最后一个属性:

const obj1 = {
  a: 10,
  b: 20
};

const obj2 = {
  ...obj1,
  a: 30
};

console.log(obj2);    // → {a: 30, b: 20}

Spread属性还提供了一种合并两个或多个对象的新方法,可以将其用作方法的替代Object.assign()方法:

const obj1 = {a: 10};
const obj2 = {b: 20};
const obj3 = {c: 30};

// ES2018
console.log({...obj1, ...obj2, ...obj3});    // → {a: 10, b: 20, c: 30}

// ES2015
console.log(Object.assign({}, obj1, obj2, obj3));    // → {a: 10, b: 20, c: 30}

但请注意,spread属性并不总是产生相同的结果Object.assign()。请考虑以下代码:

Object.defineProperty(Object.prototype, "a", {
  set(value) {
    console.log("set called!");
  }
});

const obj = {a: 10};

console.log({...obj});    
// → {a: 10}

console.log(Object.assign({}, obj));    
// → set called!
// → {}

在此代码中,该Object.assign()方法执行继承的setter属性。相反,传播属性完全忽略了setter。

重要的是要记住,spread属性只复制可枚举的属性。在以下示例中,type属性不会显示在复制的对象中,因为其enumerable属性设置为false

const car = {
  color: "blue"
};

Object.defineProperty(car, "type", {
  value: "coupe",
  enumerable: false
});

console.log({...car});    // → {color: "blue"}

即使它们是可枚举的,也会忽略继承的属性:

const car = {
  color: "blue"
};

const car2 = Object.create(car, {
  type: {
    value: "coupe",
    enumerable: true,
  }
});

console.log(car2.color);                      // → blue
console.log(car2.hasOwnProperty("color"));    // → false

console.log(car2.type);                       // → coupe
console.log(car2.hasOwnProperty("type"));     // → true

console.log({...car2});                       // → {type: "coupe"}

在此代码中,car2继承color属性car。因为spread属性只复制对象的自己的属性,color所以不包含在返回值中。

请记住,spread属性只能生成对象的浅表副本。如果属性包含对象,则仅复制对象的引用:

const obj = {x: {y: 10}};
const copy1 = {...obj};    
const copy2 = {...obj}; 

console.log(copy1.x === copy2.x);    // → true

这里copy1copy2的x是指在内存中的同一对象,所以全等运算返回true

ES2015中添加的另一个有用功能是rest参数,它使JavaScript程序员可以使用它...来表示值作为数组。例如:

const arr = [10, 20, 30];
const [x, ...rest] = arr;

console.log(x);       // → 10
console.log(rest);    // → [20, 30]

这里,arr的第一个值被分配给对应的x,而剩余的元素被分配给rest变量。这种称为阵列解构的模式变得如此受欢迎,以至于Ecma技术委员会决定为对象带来类似的功能:

const obj = {
  a: 10,
  b: 20,
  c: 30
};

const {a, ...rest} = obj;

console.log(a);       // → 10
console.log(rest);    // → {b: 20, c: 30}

此代码使用解构赋值中的其余属性将剩余的自身可枚举属性复制到新对象中。请注意,rest属性必须始终出现在对象的末尾,否则会引发错误:

const obj = {
  a: 10,
  b: 20,
  c: 30
};

const {...rest, a} = obj;    // → SyntaxError: Rest element must be last element

还要记住,在对象中使用多个rest会导致错误,除非它们是嵌套的:

const obj = {
  a: 10,
  b: {
    x: 20,
    y: 30,
    z: 40
  }
};

const {b: {x, ...rest1}, ...rest2} = obj;    // no error

const {...rest, ...rest2} = obj;    // → SyntaxError: Rest element must be last element
Support for Rest/Spread
Chrome Firefox Safari Edge
60 55 11.1 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
60 55 11.3 No 8.2 60
Node.js

8.0.0(运行时需要加-harmony

8.3.0(完全支持)

Asynchronous Iteration(异步迭代)

迭代数据集是编程的重要部分。此前ES2015,提供的JavaScript语句如forfor...inwhile,和方法map()filter()以及forEach()都用于此目的。为了使程序员能够一次一个地处理集合中的元素,ES2015引入了迭代器接口。

如果对象具有Symbol.iterator属性,则该对象是可迭代的。在ES2015中,字符串和集合对象(如Set,Map和Array)带有Symbol.iterator属性,因此可以迭代。以下代码给出了如何一次访问可迭代元素的示例:

const arr = [10, 20, 30];
const iterator = arr[Symbol.iterator]();
  
console.log(iterator.next());    // → {value: 10, done: false}
console.log(iterator.next());    // → {value: 20, done: false}
console.log(iterator.next());    // → {value: 30, done: false}
console.log(iterator.next());    // → {value: undefined, done: true}

Symbol.iterator是一个众所周知的符号,指定一个返回迭代器的函数。与迭代器交互的主要方法是next()方法。此方法返回具有两个属性的对象:valuedonevalue属性为集合中下一个元素的值。done属性的值为truefalse表示集合是否迭代完成。

默认情况下,普通对象不可迭代,但如果在其上定义Symbol.iterator属性,则它可以变为可迭代,如下例所示:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.iterator]() {
    const values = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        return {
          value: this[values[i++]],
          done: i > values.length
        }
      }
    };
  }
};

const iterator = collection[Symbol.iterator]();
  
console.log(iterator.next());    // → {value: 10, done: false}
console.log(iterator.next());    // → {value: 20, done: false}
console.log(iterator.next());    // → {value: 30, done: false}
console.log(iterator.next());    // → {value: undefined, done: true}

此对象是可迭代的,因为它定义了一个Symbol.iterator属性。迭代器使用该Object.keys()方法获取对象属性名称的数组,然后将其分配给values常量。它还定义了一个计数器变量i,并给它一个初始值0.当执行迭代器时,它返回一个包含next()方法的对象。每次调用next()方法时,它都返回一对{value, done}value保持集合中的下一个元素并done保持一个布尔值,指示迭代器是否已达到集合的需要。

虽然这段代码完美无缺,但却不必要。使用生成器函数可以大大简化过程:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.iterator]: function * () {
    for (let key in this) {
      yield this[key];
    }
  }
};

const iterator = collection[Symbol.iterator]();
  
console.log(iterator.next());    // → {value: 10, done: false}
console.log(iterator.next());    // → {value: 20, done: false}
console.log(iterator.next());    // → {value: 30, done: false}
console.log(iterator.next());    // → {value: undefined, done: true}

在这个生成器中,for...in循环用于枚举集合并产生每个属性的值。结果与前一个示例完全相同,但它大大缩短了。

迭代器的缺点是它们不适合表示异步数据源。ES2018的补救解决方案是异步迭代器和异步迭代。异步迭代器与传统迭代器的不同之处在于,它不是以形式返回普通对象{value, done},而是返回履行的承诺{value, done}。异步迭代定义了一个返回异步迭代器的Symbol.asyncIterator方法(而不是Symbol.iterator)。

一个例子让这个更清楚:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]() {
    const values = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        return Promise.resolve({
          value: this[values[i++]], 
          done: i > values.length
        });
      }
    };
  }
};

const iterator = collection[Symbol.asyncIterator]();
  
console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 10, done: false}
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 20, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 30, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: undefined, done: true} 
}));

请注意,不可使用promises的迭代器来实现相同的结果。虽然普通的同步迭代器可以异步确定值,但它仍然需要同步确定done的状态。

同样,您可以使用生成器函数简化过程,如下所示:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]: async function * () {
    for (let key in this) {
      yield this[key];
    }
  }
};

const iterator = collection[Symbol.asyncIterator]();
  
console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 10, done: false}
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 20, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: 30, done: false} 
}));

console.log(iterator.next().then(result => {
  console.log(result);    // → {value: undefined, done: true} 
}));

通常,生成器函数返回带有next()方法的生成器对象。当调用next()时,它返回一个{value,done},其value属性保存了yield值。异步生成器执行相同的操作,除了它返回一个履行{value,done}的promise。

迭代可迭代对象的一种简单方法是使用for...of语句,但是for...of不能与async iterables一起使用,因为valuedone不是同步确定的。因此,ES2018提供了for...await...of。我们来看一个例子:

const collection = {
  a: 10,
  b: 20,
  c: 30,
  [Symbol.asyncIterator]: async function * () {
    for (let key in this) {
      yield this[key];
    }
  }
};

(async function () {
  for await (const x of collection) {
    console.log(x);
  }
})();

// logs:
// → 10
// → 20
// → 30

在此代码中,for...await...of语句隐式调用Symbol.asyncIterator集合对象上的方法以获取异步迭代器。每次循环时,都会调用迭代器的next()方法,它返回一个promise。一旦解析了promise,就会将结果对象的value属性读取到x变量中。循环继续,直到返回的对象的done属性值为true

请记住,该for...await...of语句仅在异步生成器和异步函数中有效。违反此规则会导致一个SyntaxError报错。

next()方法可能会返回拒绝的promise。要优雅地处理被拒绝的promise,您可以将for...await...of语句包装在语句中try...catch,如下所示:

const collection = {
  [Symbol.asyncIterator]() {
    return {
      next: () => {
        return Promise.reject(new Error("Something went wrong."))
      }
    };
  }
};

(async function() {
  try {
    for await (const value of collection) {}
  } catch (error) {
    console.log("Caught: " + error.message);
  }
})();

// logs:
// → Caught: Something went wrong.
Support for Asynchronous Iterators
Chrome Firefox Safari Edge
63 57 12 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 57 12 No 8.2 63
Node.js

8.0.0(运行时需要加-harmony

8.3.0(完全支持)

Promise.prototype.finally

ES2018的另一个令人兴奋的补充是finally()方法。一些JavaScript库之前已经实现了类似的方法,这在许多情况下证明是有用的。这鼓励了Ecma技术委员会正式添加finally()到规范中。使用这个方法,程序员将能不管promise的结果如何,都能执行一个代码块。我们来看一个简单的例子:

fetch("https://www.google.com")
  .then((response) => {
    console.log(response.status);
  })
  .catch((error) => { 
    console.log(error);
  })
  .finally(() => { 
    document.querySelector("#spinner").style.display = "none";
  });

finally()无论操作是否成功,当您需要在操作完成后进行一些清理时,该方法会派上用场。在此代码中,该finally()方法只是在获取和处理数据后隐藏加载微调器。代码不是在then()catch()方法中复制最终逻辑,而是在promise被fulfilled或rejected后注册要执行的函数。

你可以使用promise.then(func,func)而不是promise.finally(func)来实现相同的结果,但你必须在fulfillment处理程序和rejection处理程序中重复相同的代码,或者为它声明一个变量:

fetch("https://www.google.com")
  .then((response) => {
    console.log(response.status);
  })
  .catch((error) => { 
    console.log(error);
  })
  .then(final, final);

function final() {
  document.querySelector("#spinner").style.display = "none";
}

then()catch()一样,finally()方法总是返回一个promise,因此可以链接更多的方法。通常,您希望使用finally()作为最后一个链,但在某些情况下,例如在发出HTTP请求时,最好链接另一个catch()以处理finally()中可能发生的错误。

Support for Promise.prototype.finally
Chrome Firefox Safari Edge
63 58 11.1 18
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 58 11.1 No 8.2 63
Node.js

10.0.0(完全支持)

新的RegExp功能

ES2018为该RegExp对象增加了四个新功能,进一步提高了JavaScript的字符串处理能力。这些功能如下:

S(DOTALL)标志

Named Capture Groups(命名捕获组)

Lookbehind Assertions(后向断言)

Unicode Property Escapes(Unicode属性转义)

S(DOTALL)标志

点(.)是正则表达式模式中的特殊字符,它匹配除换行符之外的任何字符,例如换行符( )或回车符( )。匹配所有字符(包括换行符)的解决方法是使用具有两个相反短字的字符类,例如[dD]。此字符类告诉正则表达式引擎找到一个数字(d)或非数字(D)的字符。因此,它匹配任何字符:

console.log(/one[dD]two/.test("one
two"));    // → true

ES2018引入了一种模式,其中点可用于实现相同的结果。可以使用s标志在每个正则表达式的基础上激活此模式:

console.log(/one.two/.test("one
two"));     // → false
console.log(/one.two/s.test("one
two"));    // → true

使用标志来选择新行为的好处是向后兼容性。因此,使用点字符的现有正则表达式模式不受影响。

Named Capture Groups(命名捕获组)

在一些正则表达式模式中,使用数字来引用捕获组可能会令人困惑。例如,使用/(d{4})-(d{2})-(d{2})/与日期匹配的正则表达式。由于美式英语中的日期符号与英式英语不同,因此很难知道哪个组指的是哪一天,哪个组指的是月份:

const re = /(d{4})-(d{2})-(d{2})/;
const match= re.exec("2019-01-10");

console.log(match[0]);    // → 2019-01-10
console.log(match[1]);    // → 2019
console.log(match[2]);    // → 01
console.log(match[3]);    // → 10

ES2018引入了使用(?...)语法的命名捕获组。因此,匹配日期的模式可以用不那么模糊的方式编写:

const re = /(?d{4})-(?d{2})-(?d{2})/;
const match = re.exec("2019-01-10");

console.log(match.groups);          // → {year: "2019", month: "01", day: "10"}
console.log(match.groups.year);     // → 2019
console.log(match.groups.month);    // → 01
console.log(match.groups.day);      // → 10

您可以使用k语法在模式中稍后调用命名的捕获组。例如,要在句子中查找连续的重复单词,您可以使用/(?w+)s+k/

const re = /(?w+)s+k/;
const match = re.exec("Get that that cat off the table!");        

console.log(match.index);    // → 4
console.log(match[0]);       // → that that

要将命名的捕获组插入到方法的替换字符串中replace(),您需要使用$构造。例如:

const str = "red & blue";

console.log(str.replace(/(red) & (blue)/, "$2 & $1"));    
// → blue & red

console.log(str.replace(/(?red) & (?blue)/, "$ & $"));    
// → blue & red
Lookbehind Assertions(后向断言)

ES2018为JavaScript带来了后向性断言,这些断言已经在其他正则表达式实现中可用多年。以前,JavaScript只支持超前断言。后向断言用表示(?<=...),并使您能够匹配基于模式之前的子字符串的模式。例如,如果要在不捕获货币符号的情况下以美元,英镑或欧元匹配产品的价格,则可以使用/(?<=$|£|€)d+(.d*)?/

const re = /(?<=$|£|€)d+(.d*)?/;

console.log(re.exec("199"));     
// → null

console.log(re.exec("$199"));    
// → ["199", undefined, index: 1, input: "$199", groups: undefined]

console.log(re.exec("€50"));     
// → ["50", undefined, index: 1, input: "€50", groups: undefined]

还有一个lookbehind的否定版本,用(?,只有当模式前面没有lookbehind中的模式时,负lookbehind才允许您匹配模式。例如,模式/(?匹配没有“un”前缀的可用词

这段翻译的不好,放上原文

There is also a negative version of lookbehind, which is denoted by (?. A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(? matches the word available if it does not have a "un" prefix:

Unicode Property Escapes(Unicode属性转义)

ES2018提供了一种称为Unicode属性转义的新类型转义序列,它在正则表达式中提供对完整Unicode的支持。假设您要在字符串中匹配Unicode字符㉛。虽然㉛被认为是一个数字,但是你不能将它与d速记字符类匹配,因为它只支持ASCII [0-9]字符。另一方面,Unicode属性转义可用于匹配Unicode中的任何十进制数:

const str = "㉛";

console.log(/d/u.test(str));    // → false
console.log(/p{Number}/u.test(str));     // → true

同样,如果要匹配任何Unicode字母字符,你可以使用p{Alphabetic}

const str = "ض";

console.log(/p{Alphabetic}/u.test(str));     // → true

// the w shorthand cannot match ض
  console.log(/w/u.test(str));    // → false

还有一个否定版本p{...},表示为P{...}

console.log(/P{Number}/u.test("㉛"));    // → false
console.log(/P{Number}/u.test("ض"));    // → true

console.log(/P{Alphabetic}/u.test("㉛"));    // → true
console.log(/P{Alphabetic}/u.test("ض"));    // → false

除了字母和数字之外,还有几个属性可以在Unicode属性转义中使用。您可以在当前规范提案中找到支持的Unicode属性列表。

Support for New RegExp

| Chrome | Firefox | Safari | Edge

S(DOTALL)标志 62 No 11.1 No
命名捕获组 64 No 11.1 No
后向断言 62 No No No
Unicode属性转义 64 No 11.1 No

| Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview

S(DOTALL)标志 62 No 11.3 No 8.2 62
命名捕获组 64 No 11.3 No No 64
后向断言 62 No No No 8.2 62
Unicode属性转义 64 No 11.3 No No 64
Node.js

8.3.0 (运行时需要加-harmony)

8.10.0 (support for s (dotAll) flag and lookbehind assertions)

10.0.0 (完全支持)

模板字符串

当模板字符串紧跟在表达式之后时,它被称为标记模板字符串。当您想要使用函数解析模板文字时,标记的模板会派上用场。请考虑以下示例:

function fn(string, substitute) {
  if(substitute === "ES6") {
    substitute = "ES2015"
  }
  return substitute + string[1];
}

const version = "ES6";
const result = fn`${version} was a major update`;

console.log(result);    // → ES2015 was a major update

在此代码中,调用标记表达式(它是常规函数)并传递模板文字。该函数只是修改字符串的动态部分并返回它。

在ES2018之前,标记的模板字符串具有与转义序列相关的语法限制。反斜杠后跟某些字符序列被视为特殊字符:x解释为十六进制转义符,u解释为unicode转义符,后跟一个数字解释为八进制转义符。其结果是,字符串,例如"C:xxxuuu"或者"ubuntu"被认为是由解释无效转义序列,并会抛出SyntaxError。

ES2018从标记模板中删除了这些限制,而不是抛出错误,表示无效的转义序列如下undefined

function fn(string, substitute) {
  console.log(substitute);    // → escape sequences:
  console.log(string[1]);     // → undefined
}

const str = "escape sequences:";
const result = fn`${str} ubuntu C:xxxuuu`;

请记住,在常规模板文字中使用非法转义序列仍会导致错误:

const result = `ubuntu`;
// → SyntaxError: Invalid Unicode escape sequence
Support for Template Literal Revision
Chrome Firefox Safari Edge
62 56 11 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
62 56 11 No 8.2 62
Node.js

8.3.0 (运行时需要加-harmony

8.10.0(完全支持)

总结

我们已经仔细研究了ES2018中引入的几个关键特性,包括异步迭代,rest/spread属性Promise.prototype.finally()以及RegExp对象的添加。虽然其中一些浏览器供应商尚未完全实现其中一些功能,但由于像Babel这样的JavaScript转换器,它们今天仍然可以使用。

ECMAScript正在迅速发展,并且每隔一段时间就会引入新功能,因此请查看已完成提案的列表,了解新功能的全部内容。

第一次翻译文章,能力有限,水平一般,翻译不妥之处,还望指正。感谢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101058.html

相关文章

  • 2019,开发应该学习的16个JavaScript框架

    摘要:它不仅从前端移动到后端,我们也开始看到它用于机器学习和增强现实,简称。由于其高使用率,年的现状调查将其称为采用的安全技术。机器学习框架在年的开发者峰会上,宣布了他们的机器学习框架的实现,称为。更高级别的用于在之上构建机器学习模型。 2019,开发者应该学习的16个JavaScript框架 showImg(https://segmentfault.com/img/remote/14600...

    Harpsichord1207 评论0 收藏0
  • javascript引擎——V8

    摘要:类将源代码解释并构建成抽象语法树,使用类来创建它们,并使用类来分配内存。类抽象语法树的访问者类,主要用来遍历抽象语法树。在该函数中,先使用类来生成抽象语法树再使用类来生成本地代码。 通过上一篇文章,我们知道了JavaScript引擎是执行JavaScript代码的程序或解释器,了解了JavaScript引擎的基本工作原理。我们经常听说的JavaScript引擎就是V8引擎,这篇文章我们...

    luoyibu 评论0 收藏0
  • 我们应该如何(以及为什么)要将Typescript与Express、nodejs一起使用(译文

    摘要:在整篇文章中,我将解释如何使用以及为什么去使用。如果没有,这两个错误将被忽视,导致最终应用程序出现一些错误。我们可以在从其他文件导入的类中使用自动完成功能。维护的难度是和开发人员避免将大型项目迁移到的主要原因之一。 showImg(https://segmentfault.com/img/remote/1460000017062509?w=800&h=248);在我的职业生涯开始时,我...

    Winer 评论0 收藏0
  • 2018年前端开发回顾

    摘要:在整个年,看到发布版增加了许多功能,包括新的生命周期方法新的上下文指针事件延迟函数和。它在等待渲染异步响应时数据,是延迟函数背后用来管理组件的代码分割的。发布自第版开始将近年后,于年发布。 前端发展发展迅速,非常的快。 本文将回顾2018年一些重要的前端新闻,事件和 JavaScript 趋势。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! showImg(ht...

    crelaber 评论0 收藏0
  • 2019 年值得学习的顶级 JavaScript 框架与主题

    摘要:我们的目标是找出最有职业投资回报率的主题和技术。比特币在几年内增长了若干个量级。比特币倍拐点在这个图表中,每个箭头始于倍点,指向价格修正后的最低点。 showImg(https://segmentfault.com/img/remote/1460000017919159); 图:Jon Glittenberg Happy New Year 2019 (CC BY 2.0) 又到了一年的...

    legendaryedu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<