1、for in 、 for of区别

1.1 for in

遍历数组时,key为数组下标字符串;遍历对象,key为对象字段名。

缺点:

  • for in 不仅会遍历当前对象,还包括原型链上的可枚举属性(一般使用hasOwnProperty来判断)
  • for in 不适合遍历数组,主要应用为对象

1.2 for of

可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments对象,NodeList对象)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

  1. let arr = [{age: 1}, {age: 5}, {age: 100}, {age: 34}]
  2. for(let { age } of arr) {
  3. if (age > 10) {
  4. break // for of 允许中断
  5. }
  6. console.log(age)
  7. }

优点:

  • for of 仅遍历当前对象
  • 可以被中断

2、object

2.1 Object.keys

该方法返回一个给定对象的自身可枚举属性组成的数组。

  1. const obj = { a: 1, b: 2 };
  2. const keys = Object.keys(obj); // [a, b]

手写实现一个函数模拟Object.keys

  1. function getObjectKeys(obj) {
  2. const result = [];
  3. for (const prop in obj) {
  4. if (obj.hasOwnProperty(prop)) {
  5. result.push(prop);
  6. }
  7. }
  8. return result;
  9. }
  10. console.log(getObjectKeys({
  11. a: 1,
  12. b: 2
  13. }))

2.2 Object.values

该方法返回一个给定对象自身的所有可枚举属性值的数组。

  1. const obj = { a: 1, b: 2 };
  2. const keys = Object.keys(obj); // [1, 2]

手写实现一个函数模拟Object.values

  1. function getObjectValues(obj) {
  2. const result = [];
  3. for (const prop in obj) {
  4. if (obj.hasOwnProperty(prop)) {
  5. result.push(obj[prop]);
  6. }
  7. }
  8. return result;
  9. }
  10. console.log(getObjectValues({
  11. a: 1,
  12. b: 2
  13. }))

2.3 Object.entries

该方法返回一个给定对象自身可枚举属性的键值对数组。

  1. const obj = { a: 1, b: 2 };
  2. const keys = Object.entries(obj); // [ [ 'a', 1 ], [ 'b', 2 ] ]

手写实现

  1. function getObjectEntries(obj) {
  2. const result = [];
  3. for (const prop in obj) {
  4. if (obj.hasOwnProperty(prop)) {
  5. result.push([prop, obj[prop]]);
  6. }
  7. }
  8. return result;
  9. }
  10. console.log(getObjectEntries({
  11. a: 1,
  12. b: 2
  13. }))

2.4 Object.getOwnPropertyNames

该方法返回一个数组,该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串。

  1. Object.prototype.aa = '1111';
  2. const testData = {
  3. a: 1,
  4. b: 2
  5. }
  6. for (const key in testData) {
  7. console.log(key);
  8. }
  9. // 获取自身,不获取原型链
  10. console.log(Object.getOwnPropertyNames(testData)) // ['a','b']

2.5 Object.getOwnPropertyDescriptor

什么是descriptor? 对象对应的属性描述符, 是一个对象. 包含以下属性:

  • configurable。 如果为false,则任何尝试删除目标属性或修改属性特性(writable, configurable, enumerable)的行为将被无效化。所以通常属性都有特性时,可以把configurable设置为true即可。
  • writable 是否可写。设置成 false,则任何对该属性改写的操作都无效(但不会报错,严格模式下会报错),默认false。
  • enumerable。是否能在for-in循环中遍历出来或在Object.keys中列举出来。 ```javascript const object1 = {}; Object.defineProperty(object1, ‘p1’, { value: ‘111’, writable: false });

object1.p1 = ‘not’;

console.log(object1.p1); // 严格模式报错,非严格模式111

  1. <br />proxy 数据劫持:
  2. ```javascript
  3. const obj = new Proxy({}, {
  4. get: function (target, propKey, receiver) {
  5. console.log(`getting ${propKey}`); // getting something
  6. return target[propKey];
  7. },
  8. set: function (target, propKey, value, receiver) {
  9. console.log(`setting ${propKey}`); // setting something
  10. return Reflect.set(target, propKey, value, receiver);
  11. }
  12. });
  13. obj.something = 1;
  14. console.log(obj.something); // 1

2.6 Reflect(就是对js不规范语言优化)

  • 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法
  • 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

但是要注意, 通过defineProperty设置writable为false的对象, 就不能用Proxy了

  1. const target = Object.defineProperties({}, {
  2. foo: {
  3. value: 123,
  4. writable: false,
  5. configurable: false
  6. },
  7. });
  8. const proxy = new Proxy(target, {
  9. get(target, propKey) {
  10. return 'abc';
  11. }
  12. });
  13. proxy.foo

2.7 Object.assign

浅拷贝, 类似于 { …a, …b };

  1. function shallowClone(source) {
  2. const target = {};
  3. for (const i in source) {
  4. if (source.hasOwnProperty(i)) {
  5. target[i] = source[i];
  6. }
  7. }
  8. return target;
  9. }

2.8 Object.is

Object.is() 方法判断两个值是否为同一个值,同一个引用。

  1. const a = {
  2. name: 1
  3. };
  4. const b = a;
  5. console.log(Object.is(a, b)) // true
  6. console.log(Object.is({}, {})) // false

2.9 Object.create

  • Object.create()方法创建一个新的对象,并以方法的第一个参数作为新对象的proto属性的值(根据已有的对象作为原型,创建新的对象。)
  • Object.create()方法还有第二个可选参数,是一个对象,对象的每个属性都会作为新对象的自身属性,对象的属性值以descriptor(Object.getOwnPropertyDescriptor(obj, ‘key’))的形式出现,且enumerable默认为false ```javascript function Person(name, sex) { this.name = name; this.sex = sex; }

const b = Object.create(Person.prototype, { name: { value: ‘coco’, writable: true, configurable: true, enumerable: true, }, sex: { enumerable: true, get: function () { return ‘hello sex’ }, set: function (val) { console.log(‘set value:’ + val) } } })

console.log(b.name) // coco console.log(b.sex) // hello sex

  1. Object.create(null)创建一个对象,但这个对象的原型链为null,即Fn.prototype = null
  2. ```javascript
  3. const b = Object.create(null) // 返回纯{}对象,无prototype
  4. b // {}
  5. b.__proto__ // undefined
  6. b.toString() // throw error

所以当你要创建一个非常干净的对象, 没有任何原型链上的属性, 那么就使用Object.create(null). for in 遍历的时候也不需要考虑原型链属性了。

3、Promise

3.1 promise.all

  1. function PromiseAll(promiseArray) {
  2. return new Promise(function (resolve, reject) {
  3. //判断参数类型
  4. if (!Array.isArray(promiseArray)) {
  5. return reject(new TypeError('arguments muse be an array'))
  6. }
  7. let counter = 0;
  8. let promiseNum = promiseArray.length;
  9. let resolvedArray = [];
  10. for (let i = 0; i < promiseNum; i++) {
  11. // 3. 这里为什么要用Promise.resolve?
  12. Promise.resolve(promiseArray[i]).then((value) => {
  13. counter++;
  14. resolvedArray[i] = value; // 2. 这里直接Push, 而不是用索引赋值, 有问题吗
  15. if (counter == promiseNum) { // 1. 这里如果不计算counter++, 直接判断resolvedArr.length === promiseNum, 会有问题吗?
  16. // 4. 如果不在.then里面, 而在外层判断, 可以吗?
  17. resolve(resolvedArray)
  18. }
  19. }).catch(e => reject(e));
  20. }
  21. })
  22. }
  23. // 测试
  24. const pro1 = new Promise((res, rej) => {
  25. setTimeout(() => {
  26. res('1')
  27. }, 1000)
  28. })
  29. const pro2 = new Promise((res, rej) => {
  30. setTimeout(() => {
  31. res('2')
  32. }, 2000)
  33. })
  34. const pro3 = new Promise((res, rej) => {
  35. setTimeout(() => {
  36. res('3')
  37. }, 3000)
  38. })
  39. const proAll = PromiseAll([pro1, pro2, pro3])
  40. .then(res =>
  41. console.log(res) // 3秒之后打印 ["1", "2", "3"]
  42. )
  43. .catch((e) => {
  44. console.log(e)
  45. })

3.2 Promise.allSeettled

需要返回所有promise的状态和结果;

  1. function PromiseAllSettled(promiseArray) {
  2. return new Promise(function (resolve, reject) {
  3. //判断参数类型
  4. if (!Array.isArray(promiseArray)) {
  5. return reject(new TypeError('arguments muse be an array'))
  6. }
  7. let counter = 0;
  8. const promiseNum = promiseArray.length;
  9. const resolvedArray = [];
  10. for (let i = 0; i < promiseNum; i++) {
  11. Promise.resolve(promiseArray[i])
  12. .then((value) => {
  13. resolvedArray[i] = {
  14. status: 'fulfilled',
  15. value
  16. };
  17. })
  18. .catch(reason => {
  19. resolvedArray[i] = {
  20. status: 'rejected',
  21. reason
  22. };
  23. })
  24. .finally(() => {
  25. counter++;
  26. if (counter == promiseNum) {
  27. resolve(resolvedArray)
  28. }
  29. })
  30. }
  31. })
  32. }

4、Array

4.1 Array.flat

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回;

  1. const arr1 = [1, 2, [3, 4]];
  2. arr1.flat(); // [1,2,3,4]
  3. const arr2 = [1, 2, [3, 4, [5, 6]]];
  4. arr2.flat(1); // [1,2,3,4,[5,6]]

模拟实现:

  1. // 使用 reduce、concat 和递归展开无限多层嵌套的数组
  2. const arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];
  3. function flatDeep(arr, d = 1) {
  4. if (d > 0) {
  5. return arr.reduce((res, val) => {
  6. if (Array.isArray(val)) {
  7. res = res.concat(flatDeep(val, d - 1))
  8. } else {
  9. res = res.concat(val);
  10. }
  11. return res;
  12. }, [])
  13. } else {
  14. return arr.slice()
  15. }
  16. };
  17. console.log(flatDeep(arr1, Infinity))
  18. // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

不考虑深度:

  1. function flatten(arr) {
  2. let res = [];
  3. let length = arr.length;
  4. for (let i = 0; i < length; i++) {
  5. if (Object.prototype.toString.call(arr[i]) === '[object Array]') {
  6. res = res.concat(flatten(arr[i]))
  7. } else {
  8. res.push(arr[i])
  9. }
  10. }
  11. return res
  12. }
  13. // 如果数组元素都是Number类型
  14. function flatten(arr) {
  15. return arr.toString().split(',').map(item => +item)
  16. }
  17. function flatten(arr){
  18. while(arr.some(item=>Array.isArray(item))){
  19. arr = [].concat(...arr);
  20. }
  21. return arr;
  22. }

4.2 Array.includes

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

  • valueToFind,需要查找的元素值。
  • fromIndex 可选,从fromIndex 索引处开始查找 valueToFind。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,然后往后搜寻)。默认为 0。

4.3 Array.from

Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

  • arrayLike,想要转换成数组的伪数组对象或可迭代对象。
  • mapFn 可选,如果指定了该参数,新数组中的每个元素会执行该回调函数。

Array.from() 可以通过以下方式来创建数组对象:

  • 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
  • 可迭代对象(可以获取对象中的元素,如 Map和 Set 等) ```javascript console.log(Array.from(‘foo’));

console.log(Array.from([1, 2, 3], x => x + x));

const set = new Set([‘foo’, ‘bar’, ‘baz’, ‘foo’]); Array.from(set); // [ “foo”, “bar”, “baz” ]

const map = new Map([[1, 2], [2, 4], [4, 8]]); Array.from(map); // [[1, 2], [2, 4], [4, 8]]

const mapper = new Map([[‘1’, ‘a’], [‘2’, ‘b’]]); Array.from(mapper.values()); // [‘a’, ‘b’];

Array.from(mapper.keys()); // [‘1’, ‘2’];

  1. <a name="QRVE9"></a>
  2. ## 4.4 Array.of
  3. Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
  4. ```javascript
  5. Array.of(7); // [7]
  6. Array.of(1, 2, 3); // [1, 2, 3]

模拟实现:

  1. Array.of = function() {
  2. return Array.prototype.slice.call(arguments);
  3. };