当前位置:   article > 正文

JavaScript 面试题(4)_关于async/await的描述,下列说法正确的是:a,使用async声明的方法会返回一个

关于async/await的描述,下列说法正确的是:a,使用async声明的方法会返回一个

1. 什么是 async/await 及其如何工作?
async/await 是 JS 中编写异步或非阻塞代码的新方法。它建立在 Promises 之上,相对于 Promise 和回调,它的可读性和简洁度都更高。但是,在使用此功能之前,我们必须先学习 Promises 的基础知识,因为正如我之前所说,它是基于 Promise 构建的,这意味着幕后使用仍然是 Promise。

使用 Promise

function callApi() {
  return fetch("url/to/api/endpoint")
    .then(resp => resp.json())
    .then(data => {
      //do something with "data"
    }).catch(err => {
      //do something with "err"
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用 async/await
在 async/await,我们使用 try/catch 语法来捕获异常。

async function callApi() {
 try {
   const resp = await fetch("url/to/api/endpoint");
   const data = await resp.json();
   //do something with "data"
 } catch (e) {
   //do something with "err"
 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意: 使用 async 关键声明函数会隐式返回一个Promise。

const giveMeOne = async () => 1;
giveMeOne()
  .then((num) => {
    console.log(num); // logs 1
  });
  • 1
  • 2
  • 3
  • 4
  • 5

注意: await 关键字只能在 async function 中使用。在任何非 async function 的函数中使用 await 关键字都会抛出错误。await 关键字在执行下一行代码之前等待右侧表达式(可能是一个Promise)返回。

const giveMeOne = async () => 1;

function getOne() {
  try {
    const num = await giveMeOne();
    console.log(num);
  } catch (e) {
    console.log(e);
  }
}

// Uncaught SyntaxError: await is only valid in async function

async function getTwo() {
  try {
    const num1 = await giveMeOne(); // 这行会等待右侧表达式执行完成
    const num2 = await giveMeOne(); 
    return num1 + num2;
  } catch (e) {
    console.log(e);
  }
}

await getTwo(); // 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2. 展开(spread )运算符 和 剩余(Rest) 运算符有什么区别?

展开运算符(spread)是三个点(…),可以将一个数组转为用逗号分隔的参数序列。

剩余运算符也是用三个点(…)表示,它的样子看起来和展开操作符一样,但是它是用于解构数组和对象。在某种程度上,剩余元素和展开元素相反,展开元素会“展开”数组变成多个元素,剩余元素会收集多个元素和“压缩”成一个单一的元素。

function add(a, b) {
  return a + b;
};

const nums = [5, 6];
const sum = add(...nums);
console.log(sum);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在本例中,我们在调用 add 函数时使用了展开操作符,对 nums 数组进行展开。所以参数 a 的值是 5 ,b 的值是 6,所以 sum 是 11。

function add(...rest) {
  return rest.reduce((total,current) => total + current);
};

console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在本例中,我们有一个 add 函数,它接受任意数量的参数,并将它们全部相加,然后返回总数。

const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]
  • 1
  • 2
  • 3

这里,我们使用剩余操作符提取所有剩余的数组值,并将它们放入除第一项之外的其他数组中。

3. 什么是默认参数?
默认参数是在 JS 中定义默认变量的一种新方法,它在 ES6 或 ECMAScript 2015 版本中可用。

//ES5 Version
function add(a,b){
  a = a || 0;
  b = b || 0;
  return a + b;
}

//ES6 Version
function add(a = 0, b = 0){
  return a + b;
}
add(1); // returns 1 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

我们还可以在默认参数中使用解构。

function getFirst([first, ...rest] = [0, 1]) {
  return first;
}

getFirst();  // 0
getFirst([10,20,30]);  // 10

function getArr({ nums } = { nums: [1, 2, 3, 4] }){
    return nums;
}

getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们还可以使用先定义的参数再定义它们之后的参数。

function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
  callback();
}
doSomethingWithValue(); //"Hello World"
  • 1
  • 2
  • 3
  • 4

4. 什么是包装对象(wrapper object)?
我们现在复习一下 JS 的数据类型,JS 数据类型被分为两大类,基本类型和引用类型。
基本类型:Undefined,Null,Boolean,Number,String,Symbol,BigInt;
引用类型:Object,Array,Date,RegExp 等,说白了就是对象;

其中引用类型有方法和属性,但是基本类型是没有的,但我们经常会看到下面的代码:

let name = "marko";

console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"
  • 1
  • 2
  • 3
  • 4

name 类型是 string,属于基本类型,所以它没有属性和方法,但是在这个例子中,我们调用了一个 toUpperCase()方法,它不会抛出错误,还返回了对象的变量值。

原因:基本类型的值被临时转换或强制转换为对象,因此 name 变量的行为类似于对象。除 null 和 undefined 之外的每个基本类型都有自己包装对象。也就是:String,Number,Boolean,Symbol 和 BigInt。在这种情况下,name.toUpperCase()在幕后看起来如下:

console.log(new String(name).toUpperCase()); // "MARKO"
  • 1

在完成访问属性或调用方法之后,新创建的对象将立即被丢弃。

5. 隐式和显式转换有什么区别?
隐式强制转换是一种将值转换为另一种类型的方法,这个过程是自动完成的,无需我们手动操作。
假设我们下面有一个例子。

console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12
  • 1
  • 2
  • 3

第一个 console.log 语句结果为 16。在其他语言中,这会抛出编译时错误,但在 JS 中,1 被转换成字符串,然后与+运算符连接。我们没有做任何事情,它是由 JS 自动完成。
第二个 console.log 语句结果为 1,JS 将 false 转换为 boolean 值为 0,,true 为 1,因此结果为 1。
第三个 console.log 语句结果 12,它将’2’转换为一个数字,然后乘以 6 * 2,结果是 12。

而显式强制是将值转换为另一种类型的方法,我们需要手动转换。

console.log(1 + parseInt('6'));
  • 1

在本例中,我们使用 parseInt 函数将’6’转换为 number ,然后使用+运算符将 1 和 6 相加。

6. 什么是 NaN?以及如何检查值是否为 NaN?

NaN 表示非数是 JS 中的一个值,该值是将数字转换或执行为非数字值的运算结果,因此结果为 NaN。

let a;

console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

JS 有一个内置的 isNaN 方法,用于测试值是否为 isNaN 值,但是这个函数有一个奇怪的行为。

console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true
  • 1
  • 2
  • 3
  • 4
  • 5

所有这些 console.log 语句都返回 true,即使我们传递的值不是 NaN。

在 ES6 中,建议使用 Number.isNaN 方法,因为它确实会检查该值(如果确实是 NaN),或者我们可以使自己的辅助函数检查此问题,因为在 JS 中,NaN 是唯一的值,它不等于自己。

  function checkIfNaN(value) {
    return value !== value;
  }
  • 1
  • 2
  • 3

7. 如何判断值是否为数组?
我们可以使用 Array.isArray 方法来检查值是否为数组。当传递给它的参数是数组时,它返回 true,否则返回 false。

console.log(Array.isArray(5));  // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5 })); // false
console.log(Array.isArray([])); // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果环境不支持此方法,则可以使用 polyfill 实现。

function isArray(value){
 return Object.prototype.toString.call(value) === "[object Array]"
}
  • 1
  • 2
  • 3

当然还可以使用传统的方法:

let a = []
if (a instanceof Array) {
  console.log('是数组')
} else {
  console.log('非数组')
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

8. 如何在不使用 % 运算符的情况下检查一个数字是否是偶数?
我们可以对这个问题使用按位&运算符,&对其操作数进行运算,并将其视为二进制值,然后执行与运算。

function isEven(num) {
  if (num & 1) {
    return false
  } else {
    return true
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

0 二进制数是 0001 二进制数是 0012 二进制数是 0103 二进制数是 0114 二进制数是 1005 二进制数是 1016 二进制数是 1107 二进制数是 111

以此类推…

与运算的规则如下:

aba & b000010111
因此,当我们执行 console.log(5&1)这个表达式时,结果为 1。首先,&运算符将两个数字都转换为二进制,因此 5 变为 101,1 变为 001。
然后,它使用按位怀运算符比较每个位(0 和 1)。101&001,从表中可以看出,如果 a & b 为 1,所以 5&1 结果为 1。
101 & 001101001001

首先我们比较最左边的 1&0,结果是 0;
然后我们比较中间的 0&0,结果是 0;
然后我们比较最后 1&1,结果是 1;
最后,得到一个二进制数 001,对应的十进制数,即 1
  • 1
  • 2
  • 3
  • 4

由此我们也可以算出 console.log(4 & 1) 结果为 0。知道 4 的最后一位是 0,而 0 & 1 将是 0。如果你很难理解这一点,我们可以使用递归函数来解决此问题。

function isEven(num) {
  if (num < 0 || num === 1) return false;
  if (num == 0) return true;
  return isEven(num - 2);
}
  • 1
  • 2
  • 3
  • 4
  • 5

9. 如何检查对象中是否存在某个属性?
第一种使用 in 操作符号:

const o = { 
  "prop" : "bwahahah",
  "prop2" : "hweasa"
};

console.log("prop" in o); // true
console.log("prop1" in o); // false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第二种使用 hasOwnProperty 方法,hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性:

console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false
  • 1
  • 2

第三种使用括号符号 obj[“prop”]。如果属性存在,它将返回该属性的值,否则将返回 undefined。

console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined
  • 1
  • 2

10. AJAX 是什么?
即异步的 JavaScript 和 XML,是一种用于创建快速动态网页的技术,传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。使用AJAX则不需要加载更新整个网页,实现部分内容更新。

用到 AJAX 的技术:

  • HTML - 网页结构
  • CSS - 网页的样式
  • JavaScript - 操作网页的行为和更新 DOM
  • XMLHttpRequest API - 用于从服务器发送和获取数据
  • PHP,Python,Nodejs - 某些服务器端语言

11. 如何在 JS 中创建对象?

使用对象字面量:

const o = {
  name: "前端小智",
  greeting() {
    return `Hi, 我是${this.name}`;
  }
};

o.greeting(); // "Hi, 我是前端小智"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用构造函数:

function Person(name) {
   this.name = name;
}

Person.prototype.greeting = function () {
   return `Hi, 我是${this.name}`;
}

const mark = new Person("前端小智");

mark.greeting(); // "Hi, 我是前端小智"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用 Object.create 方法:

const n = {
   greeting() {
      return `Hi, 我是${this.name}`;
   }
};

const o = Object.create(n); 
o.name = "前端小智";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

12. Object.seal 和 Object.freeze 方法之间有什么区别?

  • Object.freeze()
    Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

  • Object.seal()
    Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。

方法的相同点:

  • 对象不可能扩展,也就是不能再添加新的属性或者方法;
  • 对象已有属性不允许被删除;
  • 对象属性特性不可以重新配置

方法不同点:
Object.seal 方法生成的密封对象,如果属性是可写的,那么可以修改属性值。
Object.freeze 方法生成的冻结对象,属性都是不可写的,也就是属性值无法更改。

13. in 运算符和 Object.hasOwnProperty 方法有什么区别?

  • hasOwnPropert 方法
    hasOwnPropert()方法返回值是一个布尔值,指示对象自身属性中是否具有指定的属性,因此这个方法会忽略掉那些从原型链上继承到的属性。
Object.prototype.phone= '15345025546';

let obj = {
  name: '前端小智',
  age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • in 运算符
    如果指定的属性在指定的对象或其原型链中,则 in 运算符返回 true。
console.log('phone' in obj) // true
  • 1

可以看到 in 运算符会检查它或者其原型链是否包含具有指定名称的属性。

14. 有哪些方法可以处理 JS 中的异步代码?

回调
Promise
async/await
还有一些库:async.js, bluebird, q, co
  • 1
  • 2
  • 3
  • 4

15. 函数表达式和函数声明之间有什么区别?

hoistedFunc();
notHoistedFunc();

function hoistedFunc(){
  console.log("注意:我会被提升");
}

var notHoistedFunc = function(){
  console.log("注意:我没有被提升");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

notHoistedFunc 调用抛出异常:Uncaught TypeError: notHoistedFunc is not a function,
而 hoistedFunc 调用不会,因为 hoistedFunc 会被提升到作用域的顶部,而 notHoistedFunc 不会。

16. 调用函数,可以使用哪些方法?

  • 作为函数调用
    如果一个函数没有作为方法、构造函数、apply、call 调用时,此时 this 指向的是 window 对象(非严格模式);
//Global Scope
function add(a,b){
  console.log(this);
  return a + b;
}  

add(1,5); // 打印 "window" 对象和 6

const o = {
  method(callback){
    callback();
  }
}

o.method(function (){
    console.log(this); // 打印 "window" 对象
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 作为方法调用
    如果一个对象的属性有一个函数的值,我们就称它为方法。调用该方法时,该方法的 this 值指向该对象。
const details = {
  name : "Marko",
  getName(){
    return this.name;
  }
}

details.getName(); // Marko
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 作为构造函数的调用
    如果在函数之前使用 new 关键字调用了函数,则该函数称为构造函数。构造函数里面会默认创建一个空对象,并将 this 指向该对象。
function Employee(name, position, yearHired) {
  // 创建一个空对象 {}
  // 然后将空对象分配给“this”关键字
  // this = {};
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
  // 如果没有指定 return ,这里会默认返回 this
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 使用 apply 和 call 方法调用
    如果我们想显式地指定一个函数的 this 值,我们可以使用这些方法,这些方法对所有函数都可用。
const obj1 = {
  result:0
};
    
const obj2 = {
  result:0
};
    
    
function reduceAdd(){
   let result = 0;
   for(let i = 0, len = arguments.length; i < len; i++){
     result += arguments[i];
   }
   this.result = result;
}
    
reduceAdd.apply(obj1, [1, 2, 3, 4, 5]);  // reduceAdd 函数中的 this 对象将是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函数中的 this 对象将是 obj2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

17. 什么是缓存及它有什么作用?

缓存是建立一个函数的过程,这个函数能够记住之前计算的结果或值。使用缓存函数是为了避免在最后一次使用相同参数的计算中已经执行的函数的计算。这节省了时间,但也有不利的一面,即我们将消耗更多的内存来保存以前的结果。

18. 手动实现缓存方法?

function memoize(fn) {
  const cache = {};
  return function (param) {
    if (cache[param]) {
      console.log('cached');
      return cache[param];
    } else {
      let result = fn(param);
      cache[param] = result;
      console.log(`not cached`);
      return result;
    }
  }
}

const toUpper = (str ="")=> str.toUpperCase();

const toUpperMemoized = memoize(toUpper);

toUpperMemoized("abcdef");
toUpperMemoized("abcdef");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这个缓存函数适用于接受一个参数。我们需要改变下,让它接受多个参数。

const slice = Array.prototype.slice;
function memoize(fn) {
  const cache = {};
  return (...args) => {
    const params = slice.call(args);
    console.log(params);
    if (cache[params]) {
      console.log('cached');
      return cache[params];
    } else {
      let result = fn(...args);
      cache[params] = result;
      console.log(`not cached`);
      return result;
    }
  }
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) => numbers.reduce((total, cur) => total + cur, startingValue);

const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);

memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");

memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

19. 为什么 typeof null 返回 object?如何检查一个值是否为 null?
typeof null == ‘object’ 总是返回 true,因为这是自 JS 诞生以来 null 的实现。曾经有人提出将 typeof null == ‘object’ 修改为 typeof null == ‘null’,但是被拒绝了,因为这将导致更多的bug。

我们可以使用严格相等运算符 === 来检查值是否为 null。

function isNull(value){
  return value === null;
}
  • 1
  • 2
  • 3

20. new 关键字有什么作用?

new 关键字与构造函数一起使用以创建对象:

function Employee(name, position, yearHired) {
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

new 关键字做了 4 件事:

  • 创建空对象 {};
  • 将空对象分配给 this 值;
  • 将空对象的__proto__指向构造函数的 prototype;
  • 如果没有使用显式 return 语句,则返回 this;
function Person() { 
  this.name = '前端小智' 
}
  • 1
  • 2
  • 3

根据上面描述的,new Person()做了:

  • 创建一个空对象:var obj = {};
  • 将空对象分配给 this 值:this = obj;
  • 将空对象的__proto__指向构造函数的 prototype:this.proto = Person().prototype;
  • 返回 this:return this;

21. 什么时候不使用箭头函数? 说出三个或更多的例子?

不应该使用箭头函数一些情况:

  • 当想要函数被提升时(箭头函数是匿名的);
  • 要在函数中使用 this/arguments 时,由于箭头函数本身不具有 this/arguments,因此它们取决于外部上下文;
  • 使用命名函数(箭头函数是匿名的);
  • 使用函数作为构造函数时(箭头函数没有构造函数);
  • 当想在对象字面是以将函数作为属性添加并在其中使用对象时,因为咱们无法访问 this 即对象本身;

22. Object.freeze() 和 const 的区别是什么?

const 和 Object.freeze 是两个完全不同的概念。
const 声明一个只读的变量,一旦声明,常量的值就不可改变:

const person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
person = animal; // ERROR "person" is read-only    

`Object.freeze`适用于值,更具体地说,适用于对象值,它使对象不可变,即不能更改其属性。

let person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
Object.freeze(person);
person.name = "Lima"; //TypeError: Cannot assign to read only property 'name' of object
console.log(person); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

23. 如何在 JS 中“深冻结”对象?

如果咱们想要确保对象被深冻结,就必须创建一个递归函数来冻结对象类型的每个属性:

没有深冻结

let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
Object.freeze(person); 
person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

深冻结

function deepFreeze(object) {
    let propNames = Object.getOwnPropertyNames(object);
    for (let name of propNames) {
        let value = object[name];
        object[name] = value && typeof value === "object" ?
            deepFreeze(value) : value;
    }
    return Object.freeze(object);
}
let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

24. Iterator 是什么,有什么作用?

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 使得数据结构的成员能够按某种次序排列;
  • ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。

遍历过程:

  • 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象;
  • 第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员;
  • 第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员;
  • 不断调用指针对象的 next 方法,直到它指向数据结构的结束位置;

每一次调用 next 方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 value 和 done 两个属性的对象。其中,value 属性是当前成员的值,done 属性是一个布尔值,表示遍历是否结束。

//obj 就是可遍历的,因为它遵循了 Iterator 标准,且包含[Symbol.iterator]方法,方法函数也符合标准的 Iterator 接口规范。

//obj.[Symbol.iterator]() 就是 Iterator 遍历器
let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

25. Generator 函数是什么,有什么作用?

如果说 JavaScrip 是 ECMAScript 标准的一种具体实现、Iterator 遍历器是 Iterator 的具体实现,那么 Generator 函数可以说是 Iterator 接口的具体实现方式。

执行 Generator 函数会返回一个遍历器对象,每一次 Generator 函数里面的 yield 都相当一次遍历器对象的 next()方法,并且可以通过 next(value)方法传入自定义的 value,来改变 Generator 函数的行为。

Generator 函数可以通过配合 Thunk 函数更轻松更优雅的实现异步编程和控制流管理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/295432
推荐阅读
相关标签
  

闽ICP备14008679号