Bootstrap

第二章 ECMAScript6 扩展

2.1 内置对象的扩展

  ES6中针对JavaScript的内置对象字符串,数据和对象进行了扩展。

2.1.1 字符串的扩展

  在开发中,字符串的使用较为频繁,如字符串拼接、字符串操作等。ES6中引入了模板字符串和更多字符串操作方法。

2.1.1.1 模版字符串

  在JavaScript中,模板字符串(Template Strings)是ES6(ECMAScript 2015)引入的一种新的字符串字面量语法,它允许你嵌入表达式,并且可以使用多行字符串和字符串插值(interpolation)功能。模板字符串使用反引号(`)而不是普通的单引号(')或双引号(")来定义。

以下是一些关于模板字符串的示例代码:

const name = "Alice";
const greeting = `Hello, ${name}!`;
 
console.log(greeting); // 输出: Hello, Alice!

在这个例子中,${name} 是一个字符串插值表达式,它会被替换为变量 name 的值。

多行字符串

const multilineString = `This is a string
that spans multiple
lines.`;
 
console.log(multilineString);
// 输出:
// This is a string
// that spans multiple
// lines.

在这个例子中,模板字符串包含了换行符,因此输出的字符串也是多行的。

嵌入表达式
模板字符串内可以嵌入任意的JavaScript表达式,只要它们被 ${} 包围。

const a = 5;
const b = 10;
const result = `The sum of ${a} and ${b} is ${a + b}.`;
 
console.log(result); // 输出: The sum of 5 and 10 is 15.

在这个例子中,${a + b} 是一个嵌入的表达式,它会被计算并替换为结果 15。

函数调用
你也可以在模板字符串中调用函数。

function formatDate(date) {
  // 简单的日期格式化示例
  const monthNames = ["January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"];
  return `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
}
 
const today = new Date();
const formattedDate = `Today's date is ${formatDate(today)}.`;
 
console.log(formattedDate); // 输出类似: Today's date is October 5, 2023.

在这个例子中,formatDate 函数被调用,并且其返回值被嵌入到模板字符串中。

标签模板(Tagged Templates)
标签模板是一种高级功能,它允许你通过函数来处理模板字符串。标签模板的语法是在模板字符串之前加上一个函数名,并用反引号括起来的模板字符串作为该函数的第一个参数,后续参数是模板字符串中每个插值表达式的值。

function tag(strings, ...values) {
  console.log(strings); // 输出: [ 'Hello, ', ' world ', '!' ]
  console.log(values);  // 输出: [ 'Alice', 42 ]
  return strings.reduce((previous, current, index) => {
    return previous + (index === 0 ? '' : values[index - 1]) + current;
  });
}
 
const message = tag`Hello, ${'Alice'} world ${42}!`;
 
console.log(message); // 输出: Hello, Alice world 42!

在这个例子中,tag 函数接收一个数组 strings,它包含了模板字符串中的静态文本部分,以及一个 values 数组,它包含了所有插值表达式的值。然后,函数可以根据需要处理这些值,并返回最终的字符串。注意,在这个特定的例子中,tag 函数实际上并没有对输入做任何特殊处理,只是简单地重新组合了字符串和值。然而,标签模板的强大之处在于它们允许你编写能够处理模板字符串的自定义函数。

2.1.1.2 新增的字符串操作方法

  ES6中新增的常用字符串操作方法如下表所示。

方法描述
includes(searchString : string) : boolean返回布尔值,表示是否找到了参数字符串
startsWith(searchString : string) : boolean返回布尔值,表示参数字符串是否在原字符串头部
endWith(searchString : string) : boolean返回布尔值,表示参数字符串是否在原字符串尾部
repeat(count : number) : string返回一个新字符串,表示将原字符串重复n次
padStart(length : number , string : string) : string用于头部补全
padEnd(length : number , string : string) : string用于尾部补全
trimStart() : string消除字符串头部的空格
trimEnd() : string消除尾部的空格

在JavaScript的ES6(ECMAScript 2015)及后续版本中,引入了一些新的字符串操作方法,这些方法使得处理字符串变得更加方便和直观。以下是一些新增的字符串操作方法及其示例代码:

1. String.prototype.includes()
检查一个字符串是否包含在另一个字符串中,返回布尔值。

const str = "Hello, world!";
console.log(str.includes("world")); // 输出: true
console.log(str.includes("World")); // 输出: false,大小写敏感

2. String.prototype.startsWith()
检查一个字符串是否以指定的子字符串开头,返回布尔值。

const str = "Hello, world!";
console.log(str.startsWith("Hello")); // 输出: true
console.log(str.startsWith("world")); // 输出: false

3. String.prototype.endsWith()
检查一个字符串是否以指定的子字符串结尾,返回布尔值。

const str = "Hello, world!";
console.log(str.endsWith("world!")); // 输出: true
console.log(str.endsWith("Hello")); // 输出: false

4. String.prototype.repeat()
返回一个新的字符串,该字符串包含被连接在一起的指定数量的字符串副本。

const str = "abc";
console.log(str.repeat(3)); // 输出: "abcabcabc"

5. String.prototype.padStart()
在当前字符串的开头填充指定的字符串,直到达到指定的长度。如果当前字符串的长度已经等于或超过了指定的长度,则返回当前字符串。

const str = "5";
console.log(str.padStart(3, "0")); // 输出: "005"

6. String.prototype.padEnd()
在当前字符串的末尾填充指定的字符串,直到达到指定的长度。如果当前字符串的长度已经等于或超过了指定的长度,则返回当前字符串。

const str = "5";
console.log(str.padEnd(3, "0")); // 输出: "500"(注意:通常不会在数字后面填充,这里仅为了演示)
// 更常见的用法可能是对齐文本
console.log("abc".padEnd(10, "-")); // 输出: "abc-------"

7. String.prototype.trimStart() 和 String.prototype.trimEnd()
trimStart() 方法移除字符串头部的空白字符,trimEnd() 方法移除字符串尾部的空白字符。

const str = "   Hello, world!   ";
console.log(str.trimStart()); // 输出: "Hello, world!   "
console.log(str.trimEnd()); // 输出: "   Hello, world!"
console.log(str.trim()); // 输出: "Hello, world!"(同时移除头部和尾部的空白字符)

8. String.raw()
这是一个静态方法,它返回一个给定的模板字符串的所有字符串字面量连接后的新字符串,任何模板插值表达式都会被替换为对应的值,但所有的转义字符都会被当作普通字符处理。

const str = String.raw`\nThis is a raw string with a newline.`;
console.log(str); // 输出: "\nThis is a raw string with a newline."
// 与模板字符串对比
const templateStr = `\nThis is a template string with a newline.`;
console.log(templateStr); // 输出: (实际的换行效果)
// This is a template string with a newline.

这些方法大大增强了JavaScript中字符串处理的能力,使得开发者能够更简洁、更直观地操作字符串。

2.1.2 数组的扩展

  在JavaScript的ES6(ECMAScript 2015)及后续版本中,数组也得到了显著的扩展,引入了许多新的方法和功能,使数组操作更加便捷和强大。以下是一些数组的新增方法和功能及其示例代码:

1. Array.from()
将类似数组的对象(如NodeList)或可迭代对象(如Set和Map)转换为一个新的、真正的数组实例。

const arrayLike = {
  0: 'Hello',
  1: 'world',
  length: 2
};
 
const arr = Array.from(arrayLike);
console.log(arr); // 输出: ['Hello', 'world']

2. Array.of()
创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

const arr = Array.of(1, 2, 3);
console.log(arr); // 输出: [1, 2, 3]

3. Array.prototype.find() 和 Array.prototype.findIndex()
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。

const arr = [1, 2, 3, 4, 5];
 
const found = arr.find(element => element > 3);
console.log(found); // 输出: 4
 
const index = arr.findIndex(element => element > 3);
console.log(index); // 输出: 3

4. Array.prototype.fill()
用一个静态值填充一个数组从起始索引到终止索引内的所有元素。不包括终止索引。

const arr = [1, 2, 3, 4];
arr.fill(0, 1, 3);
console.log(arr); // 输出: [1, 0, 0, 4]

5. Array.prototype.copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。

const arr = [1, 2, 3, 4, 5];
arr.copyWithin(0, 3, 4);
console.log(arr); // 输出: [4, 2, 3, 4, 5]

6. Array.prototype.entries()、Array.prototype.keys() 和 Array.prototype.values()
这三个方法都返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对、键或值。

const arr = ['a', 'b', 'c'];
 
for (const [index, value] of arr.entries()) {
  console.log(index, value);
}
// 输出:
// 0 'a'
// 1 'b'
// 2 'c'
 
for (const index of arr.keys()) {
  console.log(index);
}
// 输出: 0 1 2
 
for (const value of arr.values()) {
  console.log(value);
}
// 输出: 'a' 'b' 'c'

7. Array.prototype.includes()
判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。

const arr = [1, 2, 3, 4, 5];
console.log(arr.includes(3)); // 输出: true
console.log(arr.includes(6)); // 输出: false

8. Array.prototype.flat() 和 Array.prototype.flatMap()
flat() 方法会递归地将数组中所有子数组的元素摊平到一个新数组中。

flatMap() 方法首先使用映射函数映射每个元素,然后将结果摊平成一个新数组。

const arr = [1, [2, [3, [4]]]];
console.log(arr.flat(Infinity)); // 输出: [1, 2, 3, 4]
 
const arr2 = [1, 2, 3, 4];
const mappedAndFlattened = arr2.flatMap(x => [x, x * 2]);
console.log(mappedAndFlattened); // 输出: [1, 2, 2, 4, 3, 6, 4, 8]

这些新的数组方法和功能为JavaScript开发者提供了更多样化的工具来处理数组,使得代码更加简洁和高效。

2.1.2.1 扩展运算符

  扩展运算符(Spread operator)在JavaScript的ES6(ECMAScript 2015)中被引入,它允许一个表达式在某些地方展开成多个元素(在数组字面量中)或多个参数(在函数调用中)。扩展运算符用三个连续的点(…)表示。以下是一些使用扩展运算符的示例代码:

1. 在数组字面量中使用扩展运算符
你可以使用扩展运算符将一个数组的所有元素展开到另一个数组中:

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6];
console.log(arr2); // 输出: [1, 2, 3, 4, 5, 6]

2. 在函数调用中使用扩展运算符
扩展运算符也可以用于函数调用时,将数组元素作为单独的参数传递给函数:

function sum(a, b, c) {
  return a + b + c;
}
 
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出: 6

3. 与对象字面量结合使用(对象扩展运算符)
虽然这不是严格意义上的“扩展运算符”的原始定义(因为对象没有像数组那样的“展开”行为),但ES2018引入了一个类似的概念,允许你从一个对象复制可枚举属性到另一个新对象:

const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2); // 输出: { a: 1, b: 2, c: 3 }

4. 在数组方法中使用扩展运算符
扩展运算符可以与数组的内置方法结合使用,例如concat、map和filter等,来简化代码:

const arr = [1, 2, 3];
 
// 使用 concat 和扩展运算符
const newArr = arr.concat([4, 5]);
const newArrWithSpread = [...arr, 4, 5]; // 更简洁的方式
 
// 使用 map 和扩展运算符
const doubled = arr.map(x => x * 2);
const doubledWithSpread = [...arr].map(x => x * 2); // 如果需要不修改原数组,可以先展开
 
// 使用 filter 和扩展运算符
const evens = arr.filter(x => x % 2 === 0);
const evensWithSpread = [...arr].filter(x => x % 2 === 0); // 同样,不修改原数组

注意:在map和filter的例子中,直接对原数组使用这些方法不会改变原数组,因为map和filter总是返回新数组。但如果你想要确保不修改任何数组(例如,在函数内部处理数组时),使用扩展运算符先复制数组是一个好习惯。

5. 在解构赋值中使用扩展运算符
扩展运算符还可以用于解构赋值中,以收集剩余的元素或属性:

const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 输出: 1
console.log(rest);  // 输出: [2, 3, 4]
 
const { a, ...restObj } = { a: 1, b: 2, c: 3 };
console.log(a);     // 输出: 1
console.log(restObj); // 输出: { b: 2, c: 3 }

这些示例展示了扩展运算符在数组和对象操作中的多种用途,它极大地简化了代码,并提高了可读性。

2.1.2.2 Array.from()方法

Array.from() 方法是 JavaScript ES6 引入的一个静态方法,用于从类似数组(array-like objects)或可迭代对象(iterable objects)创建一个新的、浅拷贝的数组实例。以下是一些使用 Array.from() 方法的示例代码:

1. 从类似数组的对象创建数组
类似数组的对象(array-like objects)具有 length 属性和索引元素,但它们不是真正的数组。Array.from() 可以将这些对象转换为真正的数组。

const arrayLike = {
  0: 'Hello',
  1: 'world',
  length: 2
};
 
const arr = Array.from(arrayLike);
console.log(arr); // 输出: ['Hello', 'world']

2. 从字符串创建数组
字符串是可迭代的,因此可以使用 Array.from() 将字符串转换为数组,其中每个元素都是字符串中的一个字符。

const str = 'hello';
const arr = Array.from(str);
console.log(arr); // 输出: ['h', 'e', 'l', 'l', 'o']

3. 从 Set 和 Map 创建数组
Set 和 Map 是 ES6 引入的集合类型,它们也是可迭代的。可以使用 Array.from() 将它们转换为数组。

const set = new Set([1, 2, 3]);
const arrFromSet = Array.from(set);
console.log(arrFromSet); // 输出: [1, 2, 3]
 
const map = new Map([['a', 1], ['b', 2]]);
const arrFromMap = Array.from(map);
console.log(arrFromMap); // 输出: [['a', 1], ['b', 2]](注意这是键值对的数组)
 
// 如果只想获取 Map 的值或键,可以使用第二个参数(一个映射函数)
const values = Array.from(map, ([key, value]) => value);
console.log(values); // 输出: [1, 2]

4. 使用映射函数
Array.from() 还可以接受一个可选的映射函数作为第二个参数,该函数会在每个元素被添加到新数组之前被调用。

const numbers = Array.from({ length: 5 }, (v, i) => i + 1);
console.log(numbers); // 输出: [1, 2, 3, 4, 5]

在这个例子中,我们创建了一个长度为 5 的数组,并使用映射函数将每个元素的索引(i)加 1 作为数组的值。

5. 指定数组中的 this 值
Array.from() 还接受一个可选的第三个参数,该参数是一个对象,其 this 值将被传递给映射函数(如果提供了映射函数)。

const obj = {
  value: 42,
  getDouble: function() {
    return this.value * 2;
  }
};
 
const arr = Array.from({ length: 2 }, function() {
  return this.getDouble();
}, obj);
 
console.log(arr); // 输出: [84, 84]

在这个例子中,映射函数中的 this 指向了 obj 对象,因此 this.getDouble() 能够正确访问 obj 上的 value 属性并返回其两倍的值。

2.1.2.3 数组遍历方法

  在JavaScript中,数组遍历是指按照一定顺序访问数组中的每个元素。ES6及之后的版本引入了一些新的数组遍历方法,使得遍历数组变得更加简洁和灵活。以下是一些常用的数组遍历方法及其代码示例:

1. forEach()
forEach() 方法对数组的每个元素执行一次提供的函数。

const arr = [1, 2, 3];
arr.forEach(function(element) {
  console.log(element);
});
// 输出: 1 2 3

2. map()
map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

const arr = [1, 2, 3];
const newArr = arr.map(function(element) {
  return element * 2;
});
console.log(newArr); // 输出: [2, 4, 6]

3. filter()
filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。

const arr = [1, 2, 3, 4, 5];
const evenArr = arr.filter(function(element) {
  return element % 2 === 0;
});
console.log(evenArr); // 输出: [2, 4]

4. reduce()
reduce() 方法对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。

const arr = [1, 2, 3, 4];
const sum = arr.reduce(function(accumulator, currentValue) {
  return accumulator + currentValue;
}, 0);
console.log(sum); // 输出: 10

5. for…of 循环
for…of 语句创建一个循环来遍历可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)的值。

const arr = [1, 2, 3];
for (const element of arr) {
  console.log(element);
}
// 输出: 1 2 3

6. every()
every() 方法测试数组的所有元素是否都通过了指定函数的测试。

const arr = [2, 4, 6, 8];
const allEven = arr.every(function(element) {
  return element % 2 === 0;
});
console.log(allEven); // 输出: true

7. some()
some() 方法测试数组中的某些元素是否通过了指定函数的测试。

const arr = [2, 3, 4, 5];
const someEven = arr.some(function(element) {
  return element % 2 === 0;
});
console.log(someEven); // 输出: true

8. find()
find() 方法返回数组中满足提供的测试函数的第一个元素的值。

const arr = [1, 2, 3, 4];
const found = arr.find(function(element) {
  return element > 2;
});
console.log(found); // 输出: 3

9. findIndex()
findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。

const arr = [1, 2, 3, 4];
const index = arr.findIndex(function(element) {
  return element > 2;
});
console.log(index); // 输出: 2

这些方法提供了丰富的数组遍历和处理的手段,可以根据具体需求选择合适的方法。

2.1.3 对象的扩展

  在JavaScript ES6及之后的版本中,对象的扩展引入了一些新的语法和特性,使得对象字面量的创建、属性的定义、复制和合并等操作变得更加简洁和强大。以下是一些对象扩展的特性和代码示例:

1. 属性简写
在ES6中,如果对象字面量的属性名和变量名相同,可以省略属性名,只写变量名(属性简写)。

const name = 'Alice';
const age = 25;
 
const person = {
  name, // 等同于 name: name
  age  // 等同于 age: age
};
 
console.log(person); // 输出: { name: 'Alice', age: 25 }

2. 计算属性名
在对象字面量中,可以使用方括号[]包裹一个表达式,该表达式的计算结果将作为属性名。

const propKey = 'foo';
const obj = {
  [propKey]: 'bar',
  ['a' + 'b']: 123
};
 
console.log(obj); // 输出: { foo: 'bar', ab: 123 }

3. 方法的简写
在对象字面量中定义方法时,可以省略function关键字和冒号:。

const obj = {
  sayHello() {
    console.log('Hello!');
  }
};
 
obj.sayHello(); // 输出: Hello!

4. Object.assign()
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
 
Object.assign(target, source1, source2);
 
console.log(target); // 输出: { a: 1, b: 2, c: 3 }

5. 对象属性的可枚举性和遍历
Object.keys()、Object.values()和Object.entries()方法分别返回对象的所有可枚举属性键、值、键值对数组。

const obj = { a: 1, b: 2, c: 3 };
 
console.log(Object.keys(obj));    // 输出: ['a', 'b', 'c']
console.log(Object.values(obj));  // 输出: [1, 2, 3]
console.log(Object.entries(obj)); // 输出: [['a', 1], ['b', 2], ['c', 3]]

6. Object.is()
Object.is()方法用来比较两个值是否严格相等,与严格相等运算符(===)的行为基本一致,但在处理NaN和+0与-0时不同。

console.log(Object.is('foo', 'foo')); // 输出: true
console.log(Object.is({}, {}));      // 输出: false
console.log(Object.is(+0, -0));      // 输出: false
console.log(Object.is(NaN, NaN));    // 输出: true

7. Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor()方法返回指定对象上一个自有属性(非继承属性)的描述符,如果没有该属性,则返回undefined。

const obj = {
  a: 1,
  get b() {
    return 2;
  }
};
 
const desc = Object.getOwnPropertyDescriptor(obj, 'a');
 
console.log(desc); // 输出: { value: 1, writable: true, enumerable: true, configurable: true }

8. Object.defineProperty()
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或修改一个对象的现有属性,并返回这个对象。

const obj = {};
 
Object.defineProperty(obj, 'a', {
  value: 1,
  writable: false,
  enumerable: true,
  configurable: false
});
 
console.log(obj.a); // 输出: 1
obj.a = 2;         // 尝试修改属性值,但不会生效
console.log(obj.a); // 仍然输出: 1

这些特性极大地丰富了JavaScript中对象的操作方式,使得开发者能够更灵活、更简洁地处理对象。

2.1.3.1 属性和函数的简洁表示法

  在JavaScript ES6及更高版本中,属性和函数的简洁表示法是对象字面量语法的一部分,它们使得代码更加简洁和易读。以下是一些关于属性和函数简洁表示法的代码示例:

属性简洁表示法
在ES6之前,如果你想要在一个对象字面量中定义一个属性,你需要显式地写出属性名和值,即使它们有相同的名字:

// ES5及之前
const name = 'Alice';
const obj = {
  name: name // 这里需要重复写出'name'
};

在ES6中,如果属性名和变量名相同,你可以省略属性名,只写变量名(属性简洁表示法):

// ES6及之后
const name = 'Alice';
const obj = {
  name // 这里省略了': name',属性名自动从变量名推断
};

函数简洁表示法
在ES6之前,对象字面量中的方法需要写成函数表达式的形式,并且需要指定function关键字:

// ES5及之前
const obj = {
  sayHello: function() {
    console.log('Hello!');
  }
};

在ES6中,你可以省略function关键字和冒号:,直接写出方法名和参数列表(函数简洁表示法):

// ES6及之后
const obj = {
  sayHello() { // 这里省略了'function'关键字和': function()'
    console.log('Hello!');
  }
};

综合示例
以下是一个综合使用属性简洁表示法和函数简洁表示法的示例:

javascript
const firstName = 'John';
const lastName = 'Doe';
const greet = function() {
  console.log(`Hello, my name is ${this.firstName} ${this.lastName}`);
};
 
// 使用ES6的简洁表示法
const person = {
  firstName, // 属性简洁表示法
  lastName,  // 属性简洁表示法
  greet      // 函数简洁表示法
};
 
// 调用方法
person.greet(); // 输出: Hello, my name is John Doe

在这个示例中,firstName和lastName属性使用了属性简洁表示法,而greet方法使用了函数简洁表示法。这使得对象person的创建更加简洁和直观。

2.1.3.2 对象新增方法

  在JavaScript中,向对象新增方法是一个常见的操作。你可以直接在对象上定义一个新的属性,该属性的值是一个函数,从而新增一个方法。以下是一些向对象新增方法的代码示例:

示例 1: 直接在对象上新增方法

// 创建一个空对象
const myObject = {};
 
// 向对象新增一个方法
myObject.newMethod = function() {
  console.log('This is a new method!');
};
 
// 调用新增的方法
myObject.newMethod(); // 输出: This is a new method!

示例 2: 使用对象字面量新增方法(在对象创建时)

/ 使用对象字面量创建一个对象,并同时定义一个新方法
const myObject = {
  existingMethod: function() {
    console.log('This is an existing method!');
  },
  newMethod: function() {
    console.log('This is a new method defined in the object literal!');
  }
};
 
// 调用已存在的方法和新增的方法
myObject.existingMethod(); // 输出: This is an existing method!
myObject.newMethod();      // 输出: This is a new method defined in the object literal!

示例 3: 使用ES6类新增方法(如果对象是通过类创建的)
如果你的对象是通过ES6类创建的,你可以通过向类的原型添加方法来新增方法:

// 定义一个类
class MyClass {
  existingMethod() {
    console.log('This is an existing method!');
  }
}
 
// 向类的原型新增一个方法
MyClass.prototype.newMethod = function() {
  console.log('This is a new method added to the class prototype!');
};
 
// 创建类的实例
const myInstance = new MyClass();
 
// 调用已存在的方法和新增的方法
myInstance.existingMethod(); // 输出: This is an existing method!
myInstance.newMethod();      // 输出: This is a new method added to the class prototype!
注意,向类的原型添加方法会影响到该类的所有实例,但不会影响已经通过该类创建的、并且在添加方法之前存在的实例(除非这些实例明确地调用了新的方法,并且该方法在它们的原型链上可访问)。

示例 4: 使用Object.defineProperty新增方法
你还可以使用Object.defineProperty方法来向对象新增一个方法,并可以指定该方法的属性特性(如可枚举性、可写性和可配置性):

javascript
复制代码
const myObject = {};
 
// 使用Object.defineProperty新增一个方法
Object.defineProperty(myObject, 'newMethod', {
  value: function() {
    console.log('This is a new method added using Object.defineProperty!');
  },
  writable: true,
  enumerable: true,
  configurable: true
});
 
// 调用新增的方法
myObject.newMethod(); // 输出: This is a new method added using Object.defineProperty!

在大多数情况下,直接使用对象字面量或直接在对象上定义新方法是更简单和更直接的方式。然而,在某些高级用例中,Object.defineProperty提供了更细粒度的控制。

2.1.3.3 对象遍历方法

在ES6中对象的遍历方法和数组的遍历方法基本相同,具体如下表所示。

方法描述
keys() : iterator返回参数对象自身的所有可遍历属性的键名
values() : iterator返回参数对象自身的所有可遍历属性的键值
entries() : iterator返回参数对象自身的所有可遍历属性的键值对数组

  在JavaScript中,遍历对象(即访问对象的所有可枚举属性)有多种方法。以下是几种常用的对象遍历方法及其代码示例:

1. for…in 循环
for…in 循环用于遍历对象的可枚举属性(包括原型链上的属性,除非使用 hasOwnProperty 方法进行过滤)。

const obj = {
  a: 1,
  b: 2,
  c: 3
};
 
for (let key in obj) {
  if (obj.hasOwnProperty(key)) { // 过滤掉原型链上的属性
    console.log(`${key}: ${obj[key]}`);
  }
}
// 输出:
// a: 1
// b: 2
// c: 3

2. Object.keys() 方法
Object.keys() 方法返回一个数组,该数组包含对象的所有可枚举属性名(不包括原型链上的属性)。

const obj = {
  a: 1,
  b: 2,
  c: 3
};
 
Object.keys(obj).forEach(key => {
  console.log(`${key}: ${obj[key]}`);
});
// 输出:
// a: 1
// b: 2
// c: 3

3. Object.getOwnPropertyNames() 方法
Object.getOwnPropertyNames() 方法返回一个数组,该数组包含对象的所有属性名(包括不可枚举属性,但不包括 Symbol 类型的属性,也不包括原型链上的属性)。

const obj = {
  a: 1,
  b: 2,
  [Symbol('c')]: 3, // Symbol类型的属性不会被返回
  d: 4, // 假设d是不可枚举属性,这里仅为示例,实际中需要定义为不可枚举
};
Object.defineProperty(obj, 'd', { enumerable: false }); // 将d设置为不可枚举
 
Object.getOwnPropertyNames(obj).forEach(key => {
  console.log(key); // 注意:这里只打印属性名,不打印属性值
});
// 输出:
// a
// b
// d(即使d是不可枚举的,也会被返回)
// 注意:Symbol('c')不会被返回

4. Object.getOwnPropertySymbols() 方法
Object.getOwnPropertySymbols() 方法返回一个数组,该数组包含对象上所有的 Symbol 类型的属性键。

const obj = {
  [Symbol('a')]: 1,
  [Symbol('b')]: 2
};
 
Object.getOwnPropertySymbols(obj).forEach(sym => {
  console.log(sym.toString(), obj[sym]);
});
// 输出:
// Symbol(a) 1
// Symbol(b) 2

5. Reflect.ownKeys() 方法
Reflect.ownKeys() 方法返回一个数组,该数组包含对象自身的所有属性键(包括 Symbol 类型的属性键和不可枚举属性键,但不包括原型链上的属性键)。

const obj = {
  a: 1,
  b: 2,
  [Symbol('c')]: 3,
  d: 4 // 假设d是不可枚举属性
};
Object.defineProperty(obj, 'd', { enumerable: false }); // 将d设置为不可枚举
 
Reflect.ownKeys(obj).forEach(key => {
  if (typeof key === 'string') {
    console.log(`${key}: ${obj[key]}`);
  } else {
    console.log(key.toString(), obj[key]); // 对于Symbol类型的键,使用toString()方法转换为字符串
  }
});
// 输出:
// a: 1
// b: 2
// Symbol(c) 3
// d(即使d是不可枚举的,也会被返回,但不会打印其值,因为这里我们根据类型判断了输出格式)
// 注意:在实际输出中,对于字符串类型的键,我们直接拼接了值;对于Symbol类型的键,我们使用了toString()方法并单独打印了值。

在实际开发中,根据具体需求选择合适的方法来遍历对象属性。通常,for…in 循环和 Object.keys() 方法是最常用的。

2.2 新增Set和Map对象

  ES6提供了新的数据结构:Set对象和Map对象。

2.2.1 Set对象

Set对象的常用属性和方法如下表所示。

分类名称描述
属性Set.prototype.size返回Set实例的成员总数
操作方法Set.prototype.add(value)添加某个值,返回Set结构
Set.prototype.delete(value)删除某个值,返回一个布尔值,表示删除是否成功
Set.prototype.has(value)返回一个布尔值,表示该值是否为Set的成员
Set.prototype.clear()清除所有成员,没有返回值
遍历方法Set.prototype.keys()返回键名的遍历器
Set.prototype.values()返回键值的遍历器
Set.prototype.entries()返回键值对的遍历器
Set.prototype.forEach()使用回调函数遍历每个成员

  ES6 引入了新的数据结构 Set。Set 对象允许你存储任何类型的唯一值,无论是原始值还是对象引用。以下是一些关于如何使用 Set 对象的示例代码:

创建 Set 对象
你可以使用 new Set() 来创建一个空的 Set 对象,或者通过传递一个可迭代对象(如数组)来创建一个包含初始值的 Set 对象。

// 创建一个空的 Set 对象
let mySet = new Set();
 
// 创建一个包含初始值的 Set 对象
let mySetWithValues = new Set([1, 2, 3]);

添加和删除值
你可以使用 add() 方法向 Set 中添加值,使用 delete() 方法从 Set 中删除值。

mySet.add(1);       // 添加值 1
mySet.add(5).add(3); // 链式调用添加值 5 和 3
 
mySet.delete(1);    // 删除值 1

检查值是否存在
你可以使用 has() 方法来检查 Set 中是否包含某个值。

console.log(mySet.has(3)); // 输出: true
console.log(mySet.has(1)); // 输出: false(因为已经删除)

获取 Set 的大小
你可以使用 size 属性来获取 Set 中包含的元素数量。

console.log(mySet.size); // 输出: 当前 Set 中的元素数量

遍历 Set
你可以使用 for…of 循环来遍历 Set 中的值。

for (let value of mySet) {
  console.log(value);
}

另外,Set 对象还提供了几个用于遍历的方法:keys()、values() 和 entries()(对于 Set 来说,这三个方法返回的都是相同的迭代器对象,因为 Set 没有键和值的区别,每个元素既是键也是值)。

mySet.forEach(value => console.log(value)); // 使用 forEach 方法遍历
 
let iterator = mySet.values();
for (let value of iterator) {
  console.log(value);
}

示例:使用 Set 去重
Set 的一个常见用途是去除数组中的重复值。

let arrayWithDuplicates = [1, 2, 2, 3, 4, 4, 5];
let uniqueArray = [...new Set(arrayWithDuplicates)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

在这个例子中,我们首先创建了一个包含重复值的数组 arrayWithDuplicates,然后使用 Set 去除了重复值,并通过扩展运算符(…)将 Set 转换回了数组 uniqueArray。

注意事项

  • Set 中的值都是唯一的,如果尝试添加一个已经存在的值,Set 不会改变。
  • Set 中的元素没有特定的顺序,但是你可以通过转换为数组并使用数组的排序方法来对元素进行排序。
  • Set 对象只能存储原始值或对象引用,不能存储 undefined(尝试添加 undefined 会导致 TypeError)。但是,null 是可以存储的。

2.2.2 Map对象

分类名称描述
属性Map.prototype.size返回Map对象的成员总数
操作方法Map.prototype.set(key,value)设置键名key对应的键值为value,返回整个Map结构
Map.prototype.get(key)读取key对应的键值,如果找不到key,返回undefined
Map.prototype.has(key)返回一个布尔值,表示某个键是否在当前Map对象中
Map.prototype.delete(key)清除所有成员,没有返回值
遍历方法Map.prototype.keys()返回键名的遍历器
Map.prototype.values()返回键值的遍历器
Map.prototype.entries()返回所有成员的遍历器

  ES6 引入了 Map 对象,它是一种键值对的集合,类似于对象,但 “键” 的范围不限于字符串,任何类型的值(对象或原始值)都可以作为键。以下是一些关于如何使用 Map 对象的示例代码:

创建 Map 对象
你可以使用 new Map() 来创建一个空的 Map 对象,或者通过传递一个可迭代对象(其元素是键值对数组)来创建一个包含初始值的 Map 对象。

// 创建一个空的 Map 对象
let myMap = new Map();
 
// 创建一个包含初始值的 Map 对象
let myMapWithValues = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
  [1, 'number as key']
]);

添加和删除键值对
你可以使用 set() 方法向 Map 中添加键值对,使用 delete() 方法从 Map 中删除指定的键值对。

myMap.set('key3', 'value3');       // 添加键值对
myMap.set('key4', 42).set('key5', true); // 链式调用添加多个键值对
 
myMap.delete('key2');    // 删除键为 'key2' 的键值对

获取和检查键值对
你可以使用 get() 方法来获取与指定键相关联的值,使用 has() 方法来检查 Map 中是否包含指定的键。

console.log(myMap.get('key3')); // 输出: 'value3'
console.log(myMap.has('key1')); // 输出: true
console.log(myMap.has('key2')); // 输出: false(因为已经删除)

获取 Map 的大小
你可以使用 size 属性来获取 Map 中包含的键值对的数量。

console.log(myMap.size); // 输出: 当前 Map 中的键值对数量

遍历 Map
你可以使用 for…of 循环来遍历 Map 中的键值对。Map 对象提供了三个用于遍历的方法:keys()、values() 和 entries()。

// 使用 for...of 循环遍历键值对
for (let [key, value] of myMap) {
  console.log(key + ': ' + value);
}
 
// 使用 keys() 方法遍历所有的键
for (let key of myMap.keys()) {
  console.log(key);
}
 
// 使用 values() 方法遍历所有的值
for (let value of myMap.values()) {
  console.log(value);
}
 
// 使用 entries() 方法遍历所有的键值对(与 for...of 循环中的用法相同)
for (let [key, value] of myMap.entries()) {
  console.log(key + ': ' + value);
}
 
// 使用 forEach 方法遍历
myMap.forEach((value, key, map) => {
  console.log(key + ': ' + value);
});

示例:使用 Map 存储对象作为键
由于 Map 允许任何类型的值作为键,因此它非常适合用于存储对象作为键的情况。

let obj1 = { id: 1, name: 'Object 1' };
let obj2 = { id: 2, name: 'Object 2' };
 
let myObjectMap = new Map();
myObjectMap.set(obj1, 'Value associated with obj1');
myObjectMap.set(obj2, 'Value associated with obj2');
 
console.log(myObjectMap.get(obj1)); // 输出: 'Value associated with obj1'
console.log(myObjectMap.has(obj2)); // 输出: true

在这个例子中,我们创建了两个对象 obj1 和 obj2,并将它们作为键存储在 Map 中。然后,我们使用 get() 和 has() 方法来检索和检查与这些对象键相关联的值。

;