Web技巧(13)

发布于 大漠

任何一门语言都有自己的小技巧和新特性,比如Web的三大基石HTML、CSS和JavaScript,在各自的社区中总是有同学在不同的时候梳理一些相关的新特性和小技巧。让这些特性更好的服务于社区,尽量的让大家更好的理解和掌握。另外一个目的是,让更多的同学能把这些新特性运用于自己的项目中。在这一期中,将围绕着JavaScript的一些新特性来展开。希望对大家有所帮助。

ES2019中的新特性

在即将发布的ES2019中,会发布一些新特性,比如Object.fromEntries()trimStart()trimEnd()flat()flatMap()symbol等。在这一节中,使用一些小示例来向大家展示一下这些小特性的使用。

下面几个ES2019新特性来自@Faraz Kelhini的《5 ES2019 features you can use today》一文。

Object.fromEntries()

在JavaScript中会碰到数据格式之间的转换,前几天整理的《使用JavaScript的一些小技巧》一文中就有提到一些数据之间的相互转换。在ES2019中,如果想让Object转换成Arrays可以使用Object.fromEntries()

在ES2017中使用Object.entries()方法将一个对象转换成数组,比如:

const obj = {
    one: 1,
    two: 2,
    three: 3
}

console.log(Object.entries(obj))
// ⇒ [["one", 1], ["two", 2], ["three", 3]]

obj传递给Object.entries(),返回objectenumerablestring-keyed属性匹配的[key,value]。在ES2019中推荐使用Object.fromEntries()来实现类似的功能。该静态方法可以很容易地实现一列[key, value]转换成Object。比如:

const Array = [['one',1],['two',2],['three',3]]

console.log(Object.fromEntries(Array))
// ⇒ {one: 1, two: 2, three: 3}

如果不使用Object.fromEntries()方法要实现反转Object.entries()操作,事情会稍微复杂一些:

const myArray = [['one', 1], ['two', 2], ['three', 3]]
const obj = Array.from(myArray).reduce((acc, [key, val]) => Object.assign(acc, {[key]:val}), {})

console.log(obj)
// ⇒ {one: 1, two: 2, three: 3}

在使用Object.fromEntries()时有一点需要注意,入参可以是任何具有可迭代属性的对象,并且返回两个元素的数组对象。比如下面这个示例:

const map = new Map()
map.set('one', 1)
map.set('two', 2)
map.set('three', 3)

const obj = Object.fromEntries(map)

console.log(obj)
// ⇒ {one: 1, two: 2, three: 3}

使用Object.fromEntries()方法来转换对象同样很有用:

const obj = {
    'one': 2,
    'two': 4,
    'three': 9
}

// 将对象转换为数组(ES2017中的特性)
const arr = Object.entries(obj)
console.log(arr)
// ⇒ [["one", 2], ["two", 4], ["three", 9]]

// 求这些数的平方根
const map = arr.map(([key, val]) => [key, Math.sqrt(val)])
console.log(map)
// ⇒ [["one", 1.4142135623730951], ["two", 2], ["three", 3]]

// 将数组转换回对象
const newObj = Object.fromEntries(map)
console.log(newObj);
// ⇒ {one: 1.4142135623730951, two: 2, three: 3}

该方法还有一个方便的用途,处理urlquery查询参数的时候:

const paramsString = '?&userName="damo"&userId="98409189"';
const searchParams = new URLSearchParams(paramsString);
console.log(searchParams);
// ⇒ URLSearchParams {}

const urlQueryObj = Object.fromEntries(searchParams);
console.log(urlQueryObj);
// ⇒ {userName: ""damo"", userId: ""98409189""}

Object.fromEntries()Object.entries()的反向操作,可用于克隆对象

另外,Object.fromEntries()方法嵌入式对象和数组都只是引用:

const obj = {
    name: '大漠',
    blog: 'W3cplus',
    age: 30,
    deepCopy: {
        mutateMe: true
    }
}

const entries = Object.entries(obj)
console.log(entries)
// ⇒ [["name", "大漠"], ["blog", "W3cplus"], ["age", 30], ["deepCopy", {mutateMe: false}]]

const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// ⇒ {name: "大漠", blog: "W3cplus", age: 30, deepCopy: {mutateMe: false}}

console.log(obj.deepCopy.mutateMe)
// ⇒ true

fromEntries.deepCopy.mutateMe = false
console.log(obj.deepCopy.mutateMe)
// ⇒ false

trimStart()和trimEnd()

trimStart()trimEnd()方法从技术角度上看与trimLeft()trimRight()相似,并且将会与padStart()padEnd()具有一致标准:

const str = '      string        '

console.log(str.trimStart())    // ⇒ 'string        '
console.log(str.trimEnd())      // ⇒ '      string'

console.log(str.trimLeft())     // ⇒ 'string        '
console.log(str.trimRight())    // ⇒ '      string'

为了与padStart()padEnd()标准一致性,ES2019提出trimStart()trimEnd()trimStart()trimEnd()是作为trimLeft()trimRight()的别名。

flat()和flatMap()

flat()方法可以把一个多维数组拍平转换为一个新的一维数组:

const arr = [
    ['one', 1],
    ['two', 2],
    ['three', 3],
    4,
    [
        'five',
        [
            'six',
            6
        ],
        5
    ]
]

const flatArray = arr.flat()
console.log(flatArray)
// ⇒ ["one", 1, "two", 2, "three", 3, 4, "five", ["six", 6], 5]

flat()可以接受一个可选的参数,用来指定解构嵌套层级数,其默认值为1,比如说上面的arr数组,要完全拍平成一个一维数组,可以给flat()传一个2

const flatArray2 = arr.flat(2)
console.log(flatArray2)
// ⇒ ["one", 1, "two", 2, "three", 3, 4, "five", "six", 6, 5]

在这之前,要把一个多维数组拍平成一个一维数组,可能需要使用reduce()concat()两个方法:

[].concat.apply([], arr);
// ⇒ ["one", 1, "two", 2, "three", 3, 4, "five", ["six", 6], 5]

[].concat.apply([], [].concat.apply([], arr))
// ⇒ ["one", 1, "two", 2, "three", 3, 4, "five", "six", 6, 5]

在JavaScript中,如果要把一个二维数组拍平成一个一维数组,还可以使用...运算符,比如:

[].concat(...arr)
// ⇒ ["one", 1, "two", 2, "three", 3, 4, "five", ["six", 6], 5]

const arr2 = [].concat(...arr)
[].concat(...arr2)
// ⇒ ["one", 1, "two", 2, "three", 3, 4, "five", "six", 6, 5]

注意,在使用flat()方法时有一点要注意,如果数组中有多个空值时,使用flat()来转换数组时,将会把空值丢弃:

const arr = ['a', , , , 'b', ['c', , , 'd'], 'e']
const flatArray = arr.flat()
console.log(flatArray)
// ⇒ ["a", "b", "c", "d", "e"]

flatMap()方法组合了map()flat()两个方法,但是它的回调是返回一个数组,并且最终结果将会被展示为一个一维数组,而不是嵌套数组。

const scattered = ['my favorite', 'hamburger', 'is a', 'chicken sandwich']

// 使用map()来转换数组
const huh = scattered.map(chunk => chunk.split(' '))
console.log(huh);
// ⇒ [["my", "favorite"], ["hamburger"], ["is", "a"], ["chicken", "sandwich"]]

// 使用flat()来拍平数组
const flatHuh = huh.flat()
console.log(flatHuh)
// ⇒ ["my", "favorite", "hamburger", "is", "a", "chicken", "sandwich"]

// 使用flatMap可以一步到位实现map()和flat()的效果
const flatMapScattered = scattered.flatMap(chunk => chunk.split(' '))
console.log(flatMapScattered)
// ⇒ ["my", "favorite", "hamburger", "is", "a", "chicken", "sandwich"]

Symbol对象的描述属性

当创建一个Symbol对象时,为了方便调试,ES2019给Symbol对象增加了一个只读描述属性。

let sym = Symbol('foo')
console.log(sym.description)
// ⇒ foo

sym = Symbol()
console.log(sym.description)
// ⇒ undefined

// 创建一个全局的Symbol
sym = Symbol.for('bar')
console.log(sym.description)
// ⇒ bar

可选的catch()

catch()绑定在try...catch()方法块中将不会被使用:

try {
    // 使用浏览器可能没有实现的功能
} catch (unused) {
    // 回到已经实现的特性
}

unused在这段代码中并没有被使用到。在ES2019中,开发者可以忽略catch()绑定:

try {
    // 使用浏览器可能没有实现的功能
} catch {
    // 做一些与抛出的值无关的事情
}

下面几个ES2019新特性来自@David Neal的《What's New in JavaScript for 2019》一文。

对JavaScript类的更改

ES2019对于类有很多建议的更改,包括字段声明私有方法和字段静态方法和字段。下面是关于这些更改的示例:

class Truck extends Automobile {
    model = "Heavy Duty";   // public field declaration
    #numberOfSeats = 5;     // private field declaration
    #isCrewCab = true;
    static #name = "Truck"; // static private field declaration

    // static method
    static formattedName() {
        // Notice that the Truck class name is used
        // to access the static field instead of "this"
        return `This vehicle is a ${ Truck.#name }.`;
    }

    constructor( model, seats = 2 ) {
        super();
        this.seats = seats;
    }

    // Private method
    #getBodyType() {
        return this.#isCrewCab ? "Crew Cab" : "Standard Cab";
    }

    bodyType() {
        return `${ this.#numberOfSeats }-passenger ${ this.model } ${ this.#getBodyType() }`;
    }

    get seats() { 
        return this.#numberOfSeats; 
    }

    set seats( value ) {
        if ( value >= 1 && value < 7 ) {
            this.#numberOfSeats = value;
            this.#isCrewCab = value > 3;
        }
    }
}

用BigInt来表示更大的数值

在ES2019中可以使用BigInt来表示比253次方更大的数值。BigInt可以用一些不同的方式来声明:

const theBiggestIntegerToday = Number.MAX_SAFE_INTEGER // ⇒ 9007199254740991

// 使用n语法声明一个BigInt
const ABiggerInteger = 9100000000000001n               // ⇒ 9100000000000001n

// 使用BigInt()构造函数声明一个BigInt
const EventBiggerInteger = BigInt(9100000000000002)    // ⇒ 9100000000000002n

// 使用BigInt()构建函数声明一个BigInt,并传一个字符串
const SuchBiggerInteger = BigInt('9100000000000003')   // ⇒ 9100000000000003n

有关于BigInt更多的用例和陷阱相关的信息可以点击这里查阅

function.toString()

这个ES2019特性来自于@satansdeer的《What's New in ES10? Javascript Features ES2019》一文。

ES2019修改了function.toString()的返回值,可以返回精确的字符串,包括空格和注释。

ES2019前,使用function.toString():

function App() {/* var */}

App.toString();
// ⇒ function App() {}

在ES2019中,则变成:

function App() {
/* var */
var h = 'hello';
}

App.toString();
// ⇒ "function App() {
// ⇒   /* var */
// ⇒   var h = 'hello';
// ⇒ }"

下面几个ES2019特性来自@Rienz Otiong的《8 NEW FEATURES in JavaScript ES2019》一文。

JSON超集

JSON字符串可以包含未转义的U + 2028行分隔符 和 U + 2029段落分隔符,而 ECMAScript 字符串则不能。在 ES2019 之前,它会产生错误SyntaxError: Invalid or unexpected token

const LS = eval('"\u2028"');
const PS = eval("'\u2029'");

格式良好的JSON.stringify

ES2019 不是将未配对的代理代码点作为单个 UTF-16 代码单元返回,而是用 JSON 转义序列表示它们:

// Before
console.log(JSON.stringify("\uD800")); // ⇒ "�"

// Now ES2019
console.log(JSON.stringify("\uD800")); // ⇒ "\ud800"

下面几个ES2019特性来自@Sergey Podgornyy的《ECMAScript 10 - JavaScript this year》一文。

string.prototype.matchAll()

.matchAll()将返回所有匹配,而不是单个匹配。在.matchAll()之前,要返回多个匹配则会使用String.match正则表达式带上/g标签。

const searchString = 'olololo'

searchString.match(/o/)
// ⇒ ["o", index: 0, input: "olololo", groups: undefined]

searchString.match(/o/g)
// ⇒ ["o", "o", "o", "o"]

searchString.matchAll(/o/)
// ⇒ {_r: /o/g, _s: "olololo"}

.matchAll()返回了iterator,所以我们用for ... of处理它:

for (const item of searchString.matchAll(/o/)) {
    console.log(item);
}
// ⇒ ["o", index: 0, input: "olololo", groups: undefined]
// ⇒ ["o", index: 2, input: "olololo", groups: undefined]
// ⇒ ["o", index: 4, input: "olololo", groups: undefined]
// ⇒ ["o", index: 6, input: "olololo", groups: undefined]

.matchAll()已经隐含了/g。使用.matchAll()不需要再带上/g

.matchAll()的参数必须是正则表达式,否则会抛出异常:

searchString.matchAll('o')
// ⇒ Uncaught TypeError: o is not a regexp!

RegExp特性

JavaScript中不推荐RegExp特性,即构造函数的静态属性,如RegExp.$1以及RegExp.prototype.compile方法。在ES2019中,对于适当的RegExp的子类以及跨域RegExp的实例,禁用RegExp静态属性和RegExp.prototype.compile

动态 import

现在可以将导入分配给一个变量:

import(`./language-packs/${navigator.language}.js`);

动态import是一个异步操作。它返回一个Promise,即在加载一个模块之后,将它返回给回调函数。因此,新的绑定只能在async函数中:

element.addEventListener('click', async () => {
    const module = await import(`./eventsScripts/buttonClickEvent.js`);
    module.clickEvent();
});

它看起来像对import()函数的调用,但不是从Function.prototype中继承的。这意味着它将不可能通过callapply来调用:

import.call("example this", "argument")
// ⇒ Uncaught SyntaxError: Unexpected identifier

稳定的Array.prototype.sort()

一个稳定的排序算法是当两个具有相等键的对象在排序输出中以与未排序输入中出现的顺序相同的顺序出现时。ES2019提供稳定的阵列排序:

var fruit = [
    { name: "Apple",      count: 13, },
    { name: "Pear",       count: 12, },
    { name: "Banana",     count: 12, },
    { name: "Strawberry", count: 11, },
    { name: "Cherry",     count: 11, },
    { name: "Blackberry", count: 10, },
    { name: "Pineapple",  count: 10, }
];

// Create our own sort criteria function:
let my_sort = (a, b) => a.count - b.count;

// Perform stable ES10 sort:
let sorted = fruit.sort(my_sort);
console.log(sorted);

标准化的 globalThis 对象

ES2019之前全局this没有标准化。生产代码中,你必须手动添加如下代码来标准化多个平台的全局对象。

var getGlobal = function () {
    if (typeof self !== 'undefined') { return self; }
    if (typeof window !== 'undefined') { return window; }
    if (typeof global !== 'undefined') { return global; }
    throw new Error('unable to locate global object');
};

但即使这样也并不总是奏效。

在ES2019中添加了globalThis对象,从现在开始应该在任何平台上访问全局作用域:

// Access global array constructor
globalThis.Array(0, 1, 2);
// ⇒ [0, 1, 2]

// Similar to window.v = { flag: true } in <= ES5
globalThis.v = { flag: true };

console.log(globalThis.v);
// ⇒ { flag: true }

你不知道的DOM特性

@Louis Lazaris在他的教程《8 DOM features you didn’t know existed》中整理了8个有关于DOM操作方面的相关特性。

你肯定像下面这样给元素绑定事件:

element.addEventListener('click', doSomething, false);
  • click是监听的事件
  • doSomething是一个回调函数,它将在事件发生时执行
  • false是一个名为useCapture的布尔值,用于指示是否要使用事件冒泡和捕获

可能大家都知道怎么给元素绑定事件,但是,你可能不知道addEventListener()也接受第三个布尔值类型的参数替换为另一个新参数。这个新参数是一个看起来像这样的options对象:

element.addEventListener('click', doSomething, {
    capture: false,
    once: true,
    passive: false
});

这里定义了三个不同的属性:

  • capture:一个布尔值和useCapture参数一样
  • once:一个布尔值,如果为true,表示事件只在目标元素上运行一次,然后被删除
  • passive:一个布尔值,如果为true,表示函数不会调用preventDefault(),即使它包含在函数体中

其中最有趣的是once。在很多情况下都非常有用,可以避免使用removeEventListener()或其他复杂的技术来强制触发单个事件。有点类似于jQuery中的.one()方法。

比如下面这个示例中就使用了options对象:

上面示例中的按钮只会追回文本一次(点击按钮,只会触发一次click事件)。如果将once的值设置为false,然后多次单击该按钮,则每次单击按钮时都会附加文本(每点一次都会触发click事件)。

scrollTo()在窗口或元素中平滑滚动

点击Web页面底部“回到顶部”的按钮,然后滚动到视窗顶部。这样的需求经常见。但平滑的滚动会让用户的体验更佳。

在《改变用户体验的滚动新特性》和 《滚动的特性》中探讨过怎么改善滚动体验。

在JavaScript中,只需要使用windows.scrollTo()就可以告诉浏览器滚动到页面上指定的位置。如果为了让滚动具有平滑滚动的效果,可以给window.scrollTo()设置ScrollToOptions对象,像下面这样:

window.scrollTo({
    top: 0,
    left: 1000,
    behavior: 'smooth'
})

比如像下面这个Demo:

setTimeout()和setInterval()

在很多情况下,使用window.setTimeout()window.setInterval()执行基于时间的动画现在已经被性能更友好的window.requestAnimationFrame()所取代。但在某些情况下,setTimeout()setInterval()是更好的选择。

通常,你会看到使用他们的语法是这样的;

let timer = window.setInterval(doSomething, 3000);
function doSomething () {
    // Something happens here...
}

这里setInterval()调用传入两个参数:回调函数时间间隔。对于setTimeout(),它将运行一次,而在本例中,它将无限期地运行,直到我传入计时器ID,调用window.clearTimeout()清除定时器。

使用起来很简单。 但是,如果我希望我的回调函数接受参数呢? 最近这些计时器方法允许添加以下内容:

let timer = window.setInterval(doSomething, 3000, 10, 20);
function doSomething (a, b) {
    // Something happens here…
}

注意,我在setInterval()调用中以添加了两个参数。然后,doSomething()函数接受这些参数并根据需要操作它们。来看一个示例:

扩展阅读:

单选按钮和复选框的defaultChecked属性

对于单选按钮和复选框,如果希望获取或设置checked属性,可以使用checked属性,如下所示:

console.log(radioButton.checked); // ⇒ true
radioButton.checked = false;
console.log(radioButton.checked); // ⇒ false

但还有一个名为defaultChecked的属性,它可以应用于单选按钮或复选框组,来获得组中最初设置为checked的元素是哪个:

<form id="form">
    <input type="radio" value="one" name="setOne"> One
    <input type="radio" value="two" name="setOne" checked> Two<br />
    <input type="radio" value="three" name="setOne"> Three
</form>

有了这个,即使在更改了选中的单选按钮之后,我也可以遍历单选框并找出初始为checked是哪一个,如下所示:

for (i of myForm.setOne) {
    if (i.defaultChecked === true) {
        console.log(‘i.value’);
    }
}

该示例中的 defaultChecked 始终是单选框Two。如前所述,这也可以通过复选框组来完成。尝试更改HTML中的默认选中选项,然后再次尝试点击按钮。

在本例中,你将注意到默认情况下选中了两个复选框,因此当使用 defaultChecked 查询时,这两个复选框都将返回 true

使用 normalize() 和 wholeText() 操作文本节点

HTML文档中的文本节点可能比较复杂,特别是在动态插入或创建节点时。例如,如果我有以下HTML:

<p id="el">This is the initial text.</p>

可以给p元素增加一个文本节点:

let el = document.getElementById('el');
el.appendChild(document.createTextNode(' Some more text.'));
console.log(el.childNodes.length); // 2

注意,在添加文本节点之后,我将记录该段中子节点的长度,它表示有两个节点。这些节点是一个文本字符串,但是因为文本是动态附加的,所以它们被视为单独的节点。

在某些情况下,如果将文本作为单个文本节点处理将会更有帮助,这使得文本更容易操作。这就是 normalize()wholeText() 发挥作用的地方。

normalize() 方法可用于合并单独的文本节点:

el.normalize();
console.log(el.childNodes.length); // 1

对元素调用 normalize() 将合并该元素内的任何相邻文本节点。如果恰好有一些HTML穿插在相邻的文本节点中,HTML将保持原样,而所有相邻的文本节点将被合并。

但是,如果由于某种原因我想保持文本节点分开,但我仍然希望能够将文本作为单个单元抓取,那么这就是 wholeText() 有用的地方。 因此,我可以在相邻的文本节点上执行此操作,而不是调用 normalize()

console.log(el.childNodes[0].wholeText);
// This is the initial text. Some more text.
console.log(el.childNodes.length); // 2

只要我没有调用 normalize(),文本节点的长度将保持为2,并且我可以使用 wholeText() 记录整个文本。但是请注意几点:

  • 我必须在一个文本节点上调用 wholeText,而不是元素( el.childNodes[0] 或者 el.childNodes[1])
  • 文本节点必须相邻,中间没有HTML分隔它们

insertAdjacentElement() 和 insertAdjacentText()

insertAdjacentHTML()允许您轻松地将一串文本或HTML添加到页面中与其他元素相关的特定位置。但是,可能您没有注意到该规范还包含两个相关的方法,它们以类似的方式工作: insertAdjacentElement()insertAdjacentText()

insertAdjacentHTML() 的一个缺点是插入的内容必须是字符串的形式。 因此,如果您包含HTML,则必须将其声明为:

el.insertAdjacentHTML('beforebegin', '<p><b>Some example</b> text goes here.</p>');

但是,使用 insertAdjacentElement(),第二个参数可以是一个元素的引用,如下:

let el = document.getElementById('example'),
addEl = document.getElementById('other');
el.insertAdjacentElement('beforebegin', addEl);

insertAdjacentText() 方法的工作原理类似,但是所提供的文本字符串将仅作为文本插入,即使它包含HTML。

insertAdjacentHTML() , insertAdjacentElement(), 和 insertAdjacentText() 的第一个参数相同,可取值为:

  • beforebegin
  • afterbegin
  • beforeend
  • afterend

event.detail 属性

当使用 addEventListener() 时,您可能需要防止函数调用中的默认浏览器行为。例如,您可能想拦截 <a> 元素上的单击,并使用JavaScript处理这些单击。你会这样做:

btn.addEventListener('click', function (e) {
    // do something here...
    e.preventDefault();
}, false);

preventDefault(),和以前的 return false 效果是一样的。这需要将事件对象传递到函数中,因为该对象会调用 preventDefault() 方法。

scrollHeight 和 scrollWidth 属性

scrollHeightscrollWidth 属性可能听起来很熟悉,因为您可能会将它们与其他与宽度和高度相关的DOM功能混淆。 例如,offsetWidthoffsetHeight 属性将返回元素的高度或宽度,而不会考虑溢出。

左边列的 overflow 设置为 auto,右边列的 overflow 设置为 hiddenoffsetHeight 属性为每个属性返回相同的值,因为它不考虑可滚动区域或隐藏区域;它只测量元素的实际高度,包括任何垂直填充和边框。

另一方面,名称恰当的 scrollHeight 属性将计算元素的全部高度,包括可滚动(或隐藏)区域:

上面的示例只是它使用了 scrollHeightto 获取每个列的高度。再次注意,这两列的值是相同的。但这一次,它是一个更高的数字,因为溢出面积也被算作高度的一部分。

上面的示例主要关注元素高度,这是最常见的用例,但您也可以使用 offsetWidthscrollWidth,它们将以相同的方式应用于水平滚动。

HTML可以做什么?

Web技巧系列第八期,主要向大家展示了CSS能做什么?或者换句话说,如何使用纯CSS的特性实现一些以往需要JavaScript才能实现的交互效果。

@ananyaneogi在《HTML can do that?》一文中向大家展示了HTML可以做些什么?同时她还整理了一篇有关于CSS能做什么?但这里不表,主要聊聊HTML可以做什么?

带有可搜索文本的下拉菜单

input[type="text"]中的list值和datalist中的id绑定,可以实现带有可搜索文本的下拉菜单效果:

对话框

使用dialog实现对话框(模态框)效果:

进度条

使用progressmeter制作进度条的效果:

手风琴

使用detailssummary实现手风琴效果:

在移动端根据input类型唤起正确的键盘类型

移动端上,可以通过inputtype类型来唤起正确的键盘类型:

也可以使用inputinputmode属性来唤起键盘类型。

在Web技巧第7期中有做过相关的介绍

高亮文本

在HTML中使用mark标签,可以设置文本的高亮模式:

编辑页面内容

如果给div设置contenteditable属性,用户可以直接编译div元素的内容:

小结

在这一期中主要围绕着一些新特性或者说不少同学不了解的特性。在第一部分中主要收集了ES2019中的一些新特性,在第二部分主要围绕着DOM操作的一些不为人知的特性,第三部分是一些HTML中的特性。如果结合第7期的内容,那么有关于JavaScript、HTML和CSS的一些特性都涉及了。希望这些整理你会感兴趣,如果你在这方面有相关的经验或收集,欢迎在下面的评论中与我们一起共享。