前言
ES全称ECMAScript,ECMAScript 和 JavaScript 的关系是,前者是后者的规范,后者是前者的一种实现,从15年es6发版后,到目前的es11,es的新特性被广泛使用,成为项目开发必不可少的工具,这段时间又系统的学习了这部分的相关知识,对其归纳总结,作为这段时间学习成果的检验。
ES7-ES11新特性汇总

-
ES7新特性归纳
-
Array.prototype.includes()
-
我们如何判断一个数组是否包含一个元素?
熟悉es5的小伙伴会首先想到indexOf(),这个方法会返回当前数组元素的下标:
const arr = ["es6", "es7", "es8", "es9", "es10", "es11"] console.log(arr.indexOf("es6")) // 0 console.log(arr.indexOf("es12")) // -1亦或者:
const item = arr.find((item) => { return item === "es6" }) console.log(item) // "es6"那么这两种方式有什么弊端呢,我们往下看:
const arr = ["es6", "es7", "es8", "es9", "es10", "es11", NaN] console.log(arr.indexOf(NaN)) // -1 const item = arr.find((item) => { return item === NaN }) console.log(item) // undefined由此可以看出,es5的传统方法不满足我们的需求,无法判断数组中是否含有NaN,由此,es7提供给数组一个新的API,就是我们所说的Array.prototype.includes。
-
基本用法
const arr = ["es6", "es7", "es8", "es9", "es10", "es11",NaN] console.log(arr.includes("es6")) // true console.log(arr.includes(NaN)) // true console.log(arr.includes("es12")) // false- Array.prototype.includes():可以接收两个参数,要搜索的值和搜索的开始索引。第二个参数可选,若为负数表示从末尾开始计数下标。
- 只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些是无法判断的。
-
includes的用法和indexOf用法相似,都可以用来判断数组中是否包含一个元素,唯一的区别在于includes可以识别NaN。
-
-
幂运算符
-
我们如何求一个数的幂运算呢?
在es5中我们可以通过以下两种方式来实现:
// 通过Math.pow() console.log(Math.pow(2,53)) // 自定义pow函数 function pow(base, exponent) { let sum = 1; for (let i = 0; i <exponent; i += 1) { sum *= base } return sum } console.log(pow(2, 53)); -
es7提供了一种 ** 运算符,可以更简单实现
console.log(2**53)- 幂运算符的两个*号之间不能出现空格,前后有无空格都可以。
- 注意最大安全数:Number.MAX_SAFE_INTEGER = (2**53)-1
-
-
-
ES8新特性
-
async/await
- async/await是继es6中promise、generator后又一种更加优雅的异步编程的解决方案
- async函数是generator函数的语法糖
-
基本用法
// 不使用async/await function getPromise() { return new Promise((resolve, reject) => { setTimeout(() => { console.log(1); resolve(2); }, 1000); }); } function foo() { const res = getPromise(); console.log(res); console.log(3); } foo(); // Promise {<pending>} // 3 // 1 // 使用async/await async function foo() { const res = await getPromise(); console.log(res); console.log(3); } foo(); // 1 // 2 // 3由上面两个例子的对比就能发现,async/await可以使异步任务处理起来像是同步任务,这是因为await关键字在执行的时候会停下来,等待异步任务执行完毕(await后面一般跟的是异步任务,否则没有意义)在继续执行同步任务。
-
更优雅的异步编程的解决方案
在es6之前我们对于这个过程应该不陌生
ajax('xxx/a', res => { console.log(res) ajax('xxx/b', res => { console.log(res) ajax('xxx/c', res => { console.log(res) }) }) })这种回调之后再回调的调用方式我们称之为“回调地狱”,这种回调方式在日常开发和项目维护当中很让人头疼。我们对比下es6中Promise的处理和es8中的async/await的处理方式就知道了为什么我们称async/await为更优雅的异步编程的解决方案。
// 以下都是模拟接口请求的代码 // Promise function getPromise(url) { return new Promise((resolve, reject) => { ajax(url, res => { resolve(res) }, err => { reject(err) }) }) } getPromise('xxx/a') .then(res => { console.log(res) return getPromise('xxx/b') }).then(res => { console.log(res) return getPromise('xxx/c') }).then(res => { console.log(res) }).catch(err => { console.log(err) }) // async/await function request(url) { return new Promise(resolve => { ajax(url, res => { resolve(res) }) }) } async function getData() { let res1 = await request('xxx/a') console.log(res1) let res2 = await request('xxx/b') console.log(res2) let res3 = await request('xxx/c') console.log(res3) } getData()从两者的对比可以看出,Promise虽然将回调嵌套回调的方式改成平级调用,但是这种调用方式相比于async/await还是显得繁琐,而且async/await不存在回调。
-
Object.values()/Object.entries()
-
我们如何获取一个对象的每一个属性值?
在es5中我们常用Object,keys()及for in来直接获取
// Object,keys() const obj = { name: "张三", age: 18, sex: "male", } const values = Object.keys(obj).map(item => { return obj[item] }) console.log(values) // ["张三", 18, "male"] // for in for (let key in obj) { console.log(obj[key]) } // "张三", 18, "male"es8为我们扩展了两个新的静态方法
-
Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。
const obj = { name: "张三", age: 18, sex: "male", } console.log(Object.values(obj)) // ["张三", 18, "male"] -
Object.entries
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组。
const obj = { name: "张三", age: 18, sex: "male", } console.log(Object.entries(obj)) // [["name", "张三"],["age", "18"], ["sex", "male"]] -
for in 与Object.keys()/Object.values()/Object.entries()区别
从前面的例子可以看出for in与Object.keys()/Object.values()/Object.entries()均可以遍历对象的可枚举属性,那他们直接有什么区别呢
const obj = { name: "张三", age: 18, sex: "male", } Object.prototype.test = "test" for (let key in obj) { console.log(obj[key]) } // "张三", 18, "male","test" console.log(Object.keys(obj).map(key => obj[key])) // ["张三", 18, "male"] console.log(Object.values(obj)) // ["张三", 18, "male"] console.log(Object.entries(obj).map(([key, value]) => value)) // ["张三", 18, "male"]for in可以遍历出原型链上的可枚举属性,而Object.keys()/Object.values()/Object.entries()只能遍历自身的可枚举属性
-
如何实现一个Object.values()/Object.entries()
const obj = { name: "张三", age: 18, sex: "male", } // Object.values function values(obj) { return Object.keys(obj).map(key => obj[key]) } // Object.entries function entries(obj) { return Object.keys(obj).map(key => [key, obj[key]]) } console.log(values(obj)) console.log(entries(obj))
-
-
Object.getOwnPropertyDescriptors()
-
前面提到可枚举属性,我们怎么设置属性的值可枚举呢?
Object.defineProperty()可以通过对描述符的设置进行更精准的控制对象属性,所谓描述符:
- value [属性的值]
- writable [属性的值是否可被改变]
- enumerable [属性的值是否可被枚举]
- configurable [描述符本身是否可被修改,属性是否可被删除]
var test = { name: '测试', value: 5 } Object.defineProperty(test, "name", { enumerable: false }) for (let key in test) { console.log(key) } // value -
Object.getOwnPropertyDescriptors ()
Object.getOwnPropertyDescriptors ()可以返回对象属性的描述符
let test = { name: '测试', value: 5 } console.log(Object.getOwnPropertyDescriptors(test)) // { // name: {value: "测试", writable: true, enumerable: true, configurable: true} // value: {value: 5, writable: true, enumerable: true, configurable: true} // }Object.getOwnPropertyDescriptors(target,param)接收两个参数,返回某一个参数的描述符,通过这个方法可以实现一个Object.getOwnPropertyDescriptors ()
-
Object.getOwnPropertyDescriptors()实现
let test = { name: '测试', value: 5 } function getOwnPropertyDescriptors(obj) { const result = {}; for (let key of Reflect.ownKeys(obj)) { result[key] = Object.getOwnPropertyDescriptor(obj, key); } return result; } getOwnPropertyDescriptors(test)
-
-
String.prototype.padStart()/String.prototype.padEnd()
-
padStart()
先看一个例子,希望把当前日期格式化:yyyy-mm-dd的格式:
// 返回一个yyyy-mm-dd格式的日期 function getTime() { const date = new Date(); const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() return `${year}-${month}-${day}` } console.log(getTime()) // 2020-7-9es8 中 String 新增了两个实例函数 String.prototype.padStart() 和 String.prototype.padEnd(),允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
function getTime() { const date = new Date(); const year = date.getFullYear() const month = (date.getMonth() + 1).toString().padStart(2, "0") const day = (date.getDate()).toString().padStart(2, "0") return `${year}-${month}-${day}` } console.log(getTime()) // 2020-07-09 -
padEnd()
在正式项目中后台返回的数据中时间一般会转为时间戳格式,处理时间戳的时候单位都是ms毫秒(13位),但有时候有可能是s秒做单位(10位),这个时候我们需要做一个13位的补全,保证单位是毫秒。
time = String(time).padEnd(13, '0')
-
-
尾逗号
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号,es8 允许函数的最后一个参数有尾逗号
// es8以前 function foo(a, b, c, d) { console.log(a, b, c, d) } // es8 function foo(a, b, c, d,) { console.log(a, b, c, d) }
-
-
ES9新特性
-
for await of/Symbol.asyncIterator
es6中有一个新特性Iteartor,只要元素符合两个协议:
- 可迭代协议:对象包含Symbol.iterator属性;
- 迭代器协议:Symbol.iterator属性必须返回一个对象,这个对象包含一个next方法,且next方法也返回一个对象,此对象包含value,done两个属性
我们就可以使用for...of去遍历这个元素。
-
我们知道 for...of 可以遍历同步运行的任务,那如果是异步任务呢,如下:
function getPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time) }, time) }) } const asyncArr = [getPromise(1000), getPromise(200), getPromise(3000)] for (let item of asyncArr) { console.log(item, item.then(res => { console.log(res) })) } // Promise {<pending>} // Promise {<pending>} // Promise {<pending>} // 200 // 1000 // 3000在上述遍历的过程中可以看到三个任务是同步启动的,我们期望的是一个异步任务执行完,在执行下一个异步任务,然而从输出可以看出不是按任务的执行顺序输出的,这显然不太符合我们的要求,在 es9 中也可以用 for...await...of 来操作:
function getPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ value: time, done: false, }); }, time); }); } const asyncArr = [getPromise(1000), getPromise(200), getPromise(3000)]; asyncArr[Symbol.asyncIterator] = function () { let nextIndex = 0; return { next() { return nextIndex < asyncArr.length ? asyncArr[nextIndex++] : Promise.resolve({ value: undefined, done: true, }); }, }; }; async function test() { for await (let item of asyncArr) { console.log(Date.now(), item); } } test(); // 1594374685156 1000 // 1594374685157 200 // 1594374687157 3000await需要在async 函数或者 async 生成器里面使用
-
同步迭代器/异步迭代器
类别 同步迭代器 异步迭代器 迭代器协议 Symbol.iterator Symbol.asyncIteartor 遍历 for...of for...await...of
-
正则的扩展
-
dotAll/s
一句话总结dotAll模式就是:在正则中使用(.)字符时使用s修饰符可以解决(.)字符不能匹配行终止符的例外
console.log(/./.test(1)); console.log(/./.test("1")); console.log(/./.test("\n")); console.log(/./.test("\r")); console.log(/./.test("\u{2028}")); // true // true // false // false // false // 使用s修饰符 console.log(/./s.test(1)); console.log(/./s.test("1")); console.log(/./s.test("\n")); console.log(/./s.test("\r")); console.log(/./s.test("\u{2028}")); // true // true // true // true // true- (.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符
- 正则中可以使用的修饰符有i,g,m,y,u,s
-
具名组匹配
我们先看一个例子
console.log("2020-07-10".match(/(\d{4})-(\d{2})-(\d{2})/)); // ["2020-07-10", "2020", "07", "10", index: 0, input: "2020-07-10", groups: undefined]按照 match 的语法,没有使用 g 修饰符,所以返回值第一个数值是正则表达式的完整匹配,接下来的第二个值到第四个值是分组匹配(2020, 07, 10),我们想要获取年月日的时候不得不通过数组的下标去获取,这样显得不灵活。仔细观察 match 返回值还有几个属性,分别是 index、input、groups。
- index [匹配的结果的开始位置]
- input [匹配的字符串]
- groups [捕获组 ]
所谓的具名组匹配就是命名捕获分组:
console.log("2020-07-10".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)); // groups的值 groups: {year: "2020", month: "07", day: "10"}这样我们就可以通过groups及命名分组获取对应的年月日的值了。
-
后行断言
let test = 'world hello' console.log(test.match(/(?<=world\s)hello/))(?<)是后行断言的符号配合= 、!等使用。
-
-
对象的Rest和Spread语法
-
一个例子对比理解对象的Rest和Spread语法
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; // 数组合并 const arr = [...arr1, ...arr2]; console.log(arr); const obj1 = { a: 1 }; const obj2 = { b: 2 }; // 对象合并 const obj = { ...obj1, ...obj2 }; console.log(obj); // [1, 2, 3, 4, 5, 6] // {a: 1, b: 2}一句话总结就是(...)运算符在数组中可以怎样使用,在对象就可以怎样使用。
-
-
Promise.prototype.finally()
-
不管promise状态如何都会执行的回调函数
new Promise((resolve, reject) => { resolve(1); }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }) .finally(() => { console.log("finally"); }); // 1 // promise
-
-
带标签的模板字符串扩展
es9 新特性中移除了对 ECMAScript带标签的模板字符串中转义序列的语法限制。 遇到不合法的字符串转义返回undefined,并且从raw上可获取原字符串
function foo(str) { console.log(str); } foo`\undfdfdf`; // es9以前报错 // es9:[undefined, raw:["\undfdfdf"]]
-
-
ES10新特性
-
Object.fromEntries()
es8中对象添加了一个entries()静态方法,这个方法返回一个给定对象自身可枚举属性的键值对数组 ,Object.fromEntries()方法与 Object.entries() 正好相对,可以将键值对列表转换为一个对象 。
const obj = { x: 1, y: 2, }; const entries = Object.entries(obj); console.log(entries); console.log(Object.fromEntries(entries)); // [["x",1],["y":2]] // {x:1,y:2}只要符合entries结构的都可以使用Object.fromEntries(entries)将键值对列表转换为一个对象,比如Map
-
String.prototype.trimStart()/String.prototype.trimEnd()
-
trimStart() /trimLeft()
trimLeft是trimStart的别名,作用是去掉字符串左边的空格
-
trimEnd() / trimRight()
trimEnd是trimRight的别名,作用是去掉字符串右边的空格
const str = " hello world "; console.log(str.trimStart()); console.log(str.trimEnd()); console.log(str.trim()); // "hello world " // " hello world" // "hello world" -
-
Array.prototype.flat()/Array.prototype.flatMap()
-
Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回 。
const arr = [1, [2, [3, [4, [5, [6, 7], 8], 9]]]]; console.log(arr.flat(1)); console.log(arr.flat(5)); console.log(arr.flat(Infinity)); // [1,2,[3, [4, [5, [6, 7], 8], 9]]] // [1,2,3,4,5,6,7,8,9] // [1,2,3,4,5,6,7,8,9] -
自定义实现flat
function flat(arr, deep = 1) { const newArray = []; let deepNum = 0; const flatMap = (arr) => { arr.map((item, index, array) => { if (Array.isArray(item)) { if (deepNum < deep) { deepNum++; flatMap(item); } else { newArray.push(item); } } else { newArray.push(item); if (index === array.length - 1) deepNum = 0; } }); }; flatMap(arr); return newArray; } const arr = [1, [2, [3, [4, [5, 6], 7], 8]]]; console.log(flat(arr, 4)); -
Array.prototype.flatMap()
flatMap实质上包含两部分功能,一是map,二是flat
const numbers = [1, 2, 3]; console.log(numbers.map((x) => [x ** 2]).flat()); console.log(numbers.flatMap((x) => [x ** 2])); // [1,4,9] // [1,4,9]
-
-
Symbol.description
-
可以通过 description 获取 Symbol 的描述
const symbol = Symbol("symbol"); console.log(symbol.description); // symbol console.log(symbol.description === "symbol"); // true在es10以前,我们只能通过调用 Symbol 的 toString() 时才可以读取这个属性
console.log(symbol.toString() === "Symbol(symbol)");
-
-
Function.prototype.toString()
-
Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串
function test(a) { // es10以前不返回注释部分 console.log(a); } console.log(test.toString()); // function test(a) { // // es10以前不返回注释部分 // console.log(a); // }
-
-
catch Building
-
es10允许我们在捕获异常时省略catch的参数
// es10以前 try { throw new Error(); } catch (error) { console.log("fail"); } // es10 try { throw new Error(); } catch { console.log("fail"); }
-
-
JSON扩展
-
JSON 内容可以支持包含 U+2028行分隔符 与 U+2029段分隔符
-
在 ES10 JSON.stringify 会用转义字符的方式来处理 超出范围的 Unicode 展示错误的问题 而非编码的方式
console.log(JSON.stringify('\uD83D\uDE0E')) // 笑脸 // 单独的\uD83D其实是个无效的字符串 // 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列 console.log(JSON.stringify('\uD83D')) // "\ud83d"
-
-
-
ES11新特性
-
BigInt
es11为我们提供了第七种新的原始数据类型,对于js来说,他的最大取值范围是2的53次方
console.log(2 ** 53); console.log(2 ** 53 + 1); console.log(Number.MAX_SAFE_INTEGER); // 9007199254740992 // 9007199254740992 // 9007199254740991BigInt,表示一个任意精度的整数,可以表示超长数据,可以超出2的53次方 。
-
使用方式
// 方式一 console.log(9007199254740993); console.log(9007199254740993n); // 9007199254740992 // 9007199254740993n // 方式二 console.log(9007199254740993); console.log(BigInt(9007199254740993n)); // 9007199254740992 // 9007199254740993n- 1==1n // true
- 1 === 1n // false
- typeof 1n // bigint
- BigInt(9007199254740993n).toString() // 9007199254740993
-
-
可选链
可选链可以使我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。
const a = { b: { c: { d: { e: "111", }, }, }, }; // es11前 const value = a && a.b && a.b.c && a.b.c.d && a.b.c.d.e; console.log(value); // es11:可选链 const value2 = a?.b?.c?.d?.e; console.log(value2);可选链中的 ? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段 , 可以大量简化类似繁琐的前置校验操作 。
-
空值合并运算符
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。
const a = 0; const b = a || 1; console.log(b); // 1我们在使用||运算符时, 变量值为 0 就是 false ,所以我们会看到上述结果会输出1,但是很多时候我们希望b的输出结果就是a的值0,es11提出了空值合并运算符(??),当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
const a = 0; const b = a ?? 1; console.log(b); // 0 -
Promise.allSettled()
es6中 Promise.all方法接受一个数组元素都是由 Promise.resolve 包装的数组, 生成并返回一个新的 Promise 对象, 如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。
// 全部返回resolve function getPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time); }, time); }); } Promise.all([getPromise(1000), getPromise(2000), getPromise(3000)]).then( (res) => { console.log(res); } ); // [1000,2000,3000] // 返回reject function getPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time); }, time); }); } function getReject(time) { return new Promise((resolve, reject) => { setTimeout(() => { reject(time); }, time); }); } Promise.all([getPromise(1000), getReject(2000), getPromise(3000)]) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 2000从上面可以看出Promise.all只要有一个任务返回reject,整个任务都会失败, 我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态 ,这就是Promise.allSettled()的作用。
function getPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time); }, time); }); } function getReject(time) { return new Promise((resolve, reject) => { setTimeout(() => { reject(time); }, time); }); } Promise.allSettled([getPromise(1000), getReject(2000), getPromise(3000)]) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // [{status: "fulfilled", value: 1000}, // {status: "rejected", reason: 2000}, // {status: // "fulfilled", value: 3000}] -
import
-
按需加载
现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。
(async () => { if (somethingIsTrue) { // import module for side effects await import('xxx/xxx.js'); } })();
-
-
String.prototype.matchAll()
- matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器 ;
- 使用: str.matchAll(regexp) ;
字符串处理的一个常见场景是想要匹配出字符串中的所有目标子串,例如:
-
match()
const str = "es2015/es6 es2016/es7 es2017/es8 es2018/es9 es2019/es10 es2020/es10"; console.log(str.match(/(es\d+)\/es(\d+)/g)); // ["es2015/es6", "es2016/es7", "es2017/es8", "es2018/es9", "es2019/es10", //"es2020/es10"]match()方法中,正则表达式所匹配到的多个结果会被打包成数组返回,但无法得知每个匹配除结果之外的相关信息,比如捕获到的子串,匹配到的
index位置等 。 -
exec ()
const str = "es2015/es6 es2016/es7 es2017/es8 es2018/es9 es2019/es10 es2020/es10"; const reg = /(es\d+)\/es(\d+)/g; let matched; let formatted = []; while ((matched = reg.exec(str))) { formatted.push(`${matched[1]}-es${matched[2]}`); } console.log(formatted); //["es2015-es6","es2016-es7","es2017-es8","es2018-es9","es2019-es10", //"es2020-es10"]-
matchAll()
const str = "es2015/es6 es2016/es7 es2017/es8 es2018/es9 es2019/es10 es2020/es10"; const reg = /(es\d+)\/es(\d+)/g; const matchs = []; for (let match of str.matchAll(reg)) { matchs.push(`${match[1]}-es${match[2]}`); } console.log(matchs); // ["es2015-es6", "es2016-es7", "es2017-es8", "es2018-es9", "es2019-es10", "es2020-es10"]matchAll() 是返回一个迭代器,对大数据量的场景更友好 。
-
globalThis
Javascript 在不同的环境获取全局对象有不通的方式:
- node 中通过 global,
- web 中通过 window, self 。
es11提出的globalThis一句话总结就是: 无论是在node环境还是web中,全局作用域中的 this 可以通过globalThis访问, 不必担心它的运行环境 。
-
for...in遍历机制
JavaScript 中通过for-in遍历对象时 key 的顺序是不确定的,因为规范没有明确定义,并且能够遍历原型属性让for-in的实现机制变得相当复杂,不同 JavaScript 引擎有各自根深蒂固的不同实现,很难统一
- 所以 es11不要求统一属性遍历顺序,而是对遍历过程中的一些特殊 Case 明确定义了一些规则:
- 遍历不到 Symbol 类型的属性
- 遍历过程中,目标对象的属性能被删除,忽略掉尚未遍历到却已经被删掉的属性
- 遍历过程中,如果有新增属性,不保证新的属性能被当次遍历处理到
- 属性名不会重复出现(一个属性名最多出现一次)
- 目标对象整条原型链上的属性都能遍历到
-
相关文档: