作者:Mark A
译者:前端小智
起源:dev
点赞再看,微信搜寻 【大迁世界】 关注这个没有大厂背景,但有着一股向上踊跃心态人。本文 GitHub https://github.com/qq44924588... 上曾经收录,文章的已分类,也整顿了很多我的文档,和教程材料。

双 1111 拼团低至 85 元,来一起拼团学习搭建本人的博客吧

服务器如何搭建博客

考题列表

  • 1. undefined 和 null 有什么区别?
  • 2. && 运算符能做什么
  • 3. || 运算符能做什么
  • 4. 应用 + 或一元加运算符是将字符串转换为数字的最快办法吗?
  • 5. DOM 是什么?
  • 6. 什么是事件流传?
  • 7. 什么是事件冒泡?
  • 8. 什么是事件捕捉?
  • 9. event.preventDefault() 和 event.stopPropagation()办法之间有什么区别?
  • 10. 如何晓得是否在元素中应用了event.preventDefault()办法?
  • 11. 为什么此代码obj.someprop.x会引发谬误?
  • 12. 什么是event.target?
  • 13. 什么是event.currentTarget?
  • 14. == 和 === 有什么区别?
  • 15. 为什么在 JS 中比拟两个类似的对象时返回 false?
  • 16. !! 运算符能做什么?
  • 17. 如何在一行中计算多个表达式的值?
  • 18. 什么是晋升?
  • 19. 什么是作用域?
  • 20. 什么是闭包?
  • 21. JavaScript中的虚值是什么?
  • 22. 如何查看值是否虚值?
  • 23. 'use strict' 是干嘛用的?
  • 24. JavaScript中 this 值是什么?
  • 25. 对象的 prototype 是什么?
  • 26. 什么是IIFE,它的用处是什么?
  • 27. Function.prototype.apply办法的用处是什么?
  • 28. Function.prototype.call办法的用处是什么?
  • 29. Function.prototype.apply 和 Function.prototype.call 之间有什么区别?
  • 30. Function.prototype.bind的用处是什么?
  • 31. 什么是函数式编程? JavaScript的哪些个性使其成为函数式语言的候选语言?
  • 32. 什么是高阶函数?
  • 33. 为什么函数被称为一等公民?
  • 34. 手动实现Array.prototype.map办法
  • 35. 手动实现Array.prototype.filter办法
  • 35. 手动实现Array.prototype.reduce办法
  • 37. arguments 的对象是什么?
  • 38. 如何创立一个没有 prototype(原型) 的对象?
  • 39. 为什么在调用这个函数时,代码中的b会变成一个全局变量?
  • 40. ECMAScript是什么?

1.undefined 和 null 有什么区别?

在了解undefinednull之间的差别之前,咱们先来看看它们的类似类。

它们属于 JavaScript 的 7 种根本类型。

 let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];

它们是属于虚值,能够应用Boolean(value)!!value将其转换为布尔值时,值为false

console.log(!!null); // falseconsole.log(!!undefined); // falseconsole.log(Boolean(null)); // falseconsole.log(Boolean(undefined)); // false

接着来看看它们的区别。

undefined是未指定特定值的变量的默认值,或者没有显式返回值的函数,如:console.log(1),还包含对象中不存在的属性,这些 JS 引擎都会为其调配 undefined 值。

let _thisIsUndefined;const doNothing = () => {};const someObj = {  a : "ay",  b : "bee",  c : "si"};console.log(_thisIsUndefined); // undefinedconsole.log(doNothing()); // undefinedconsole.log(someObj["d"]); // undefined

null“不代表任何值的值”null是已明确定义给变量的值。 在此示例中,当fs.readFile办法未引发谬误时,咱们将取得null值。

fs.readFile('path/to/file', (e,data) => {   console.log(e); // 当没有谬误产生时,打印 null   if(e){     console.log(e);   }   console.log(data); });

在比拟nullundefined时,咱们应用==时失去true,应用===时失去false:

 console.log(null == undefined); // true console.log(null === undefined); // false

2. && 运算符能做什么

&& 也能够叫逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到任何虚值表达式,则返回最初一个真值表达式。它采纳短路来避免不必要的工作。

console.log(false && 1 && []); // falseconsole.log(" " && true && 5); // 5

应用if语句

const router: Router = Router();router.get('/endpoint', (req: Request, res: Response) => {   let conMobile: PoolConnection;   try {      //do some db operations   } catch (e) {   if (conMobile) {    conMobile.release();   }  }});

应用&&操作符

const router: Router = Router();router.get('/endpoint', (req: Request, res: Response) => {  let conMobile: PoolConnection;  try {     //do some db operations  } catch (e) {    conMobile && conMobile.release()  }});

3. || 运算符能做什么

||也叫或逻辑或,在其操作数中找到第一个真值表达式并返回它。这也应用了短路来避免不必要的工作。在反对 ES6 默认函数参数之前,它用于初始化函数中的默认参数值。

console.log(null || 1 || undefined); // 1function logName(name) {  var n = name || "Mark";  console.log(n);}logName(); // "Mark"

4. 应用 + 或一元加运算符是将字符串转换为数字的最快办法吗?

依据MDN文档,+是将字符串转换为数字的最快办法,因为如果值曾经是数字,它不会执行任何操作。

5. DOM 是什么?

DOM 代表文档对象模型,是 HTML 和 XML 文档的接口(API)。当浏览器第一次读取(解析)HTML文档时,它会创立一个大对象,一个基于 HTM L文档的十分大的对象,这就是DOM。它是一个从 HTML 文档中建模的树状构造。DOM 用于交互和批改DOM构造或特定元素或节点。

假如咱们有这样的 HTML 构造:

<!DOCTYPE html><html lang="en"><head>   <meta charset="UTF-8">   <meta name="viewport" content="width=device-width, initial-scale=1.0">   <meta http-equiv="X-UA-Compatible" content="ie=edge">   <title>Document Object Model</title></head><body>   <div>      <p>         <span></span>      </p>      <label></label>      <input>   </div></body></html>

等价的DOM是这样的:

JS 中的document对象示意DOM。它为咱们提供了许多办法,咱们能够应用这些办法来抉择元素来更新元素内容,等等。

6. 什么是事件流传?

事件产生在DOM元素上时,该事件并不齐全产生在那个元素上。 在“冒泡阶段”中,事件冒泡或向上流传至父级,祖父母,祖父母或父级,直到达到window为止;而在“捕捉阶段”中,事件从window开始向下触发元素 事件或event.target

事件流传有三个阶段:

  1. 捕捉阶段–事件从 window 开始,而后向下到每个元素,直到达到指标元素。
  2. 指标阶段–事件已达到目标元素。
  3. 冒泡阶段–事件从指标元素冒泡,而后回升到每个元素,直到达到 window

7. 什么是事件冒泡?

事件产生在DOM元素上时,该事件并不齐全产生在那个元素上。 在冒泡阶段,事件冒泡,或者事件产生在它的父代,祖父母,祖父母的父代,直到达到window为止。

假如有如下的 HTML 构造:

<div class="grandparent">  <div class="parent">    <div class="child">1</div>  </div></div>

对应的 JS 代码:

function addEvent(el, event, callback, isCapture = false) {  if (!el || !event || !callback || typeof callback !== 'function') return;  if (typeof el === 'string') {    el = document.querySelector(el);  };  el.addEventListener(event, callback, isCapture);}addEvent(document, 'DOMContentLoaded', () => {  const child = document.querySelector('.child');  const parent = document.querySelector('.parent');  const grandparent = document.querySelector('.grandparent');  addEvent(child, 'click', function (e) {    console.log('child');  });  addEvent(parent, 'click', function (e) {    console.log('parent');  });  addEvent(grandparent, 'click', function (e) {    console.log('grandparent');  });  addEvent(document, 'click', function (e) {    console.log('document');  });  addEvent('html', 'click', function (e) {    console.log('html');  })  addEvent(window, 'click', function (e) {    console.log('window');  })});

addEventListener办法具备第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中产生,如果为true,则事件将在捕捉阶段中产生。 如果单击child元素,它将别离在管制台上记录childparentgrandparenthtmldocumentwindow,这就是事件冒泡。

8. 什么是事件捕捉?

当事件产生在 DOM 元素上时,该事件并不齐全产生在那个元素上。在捕捉阶段,事件从window开始,始终到触发事件的元素。

假如有如下的 HTML 构造:

<div class="grandparent">  <div class="parent">    <div class="child">1</div>  </div></div>

对应的 JS 代码:

function addEvent(el, event, callback, isCapture = false) {  if (!el || !event || !callback || typeof callback !== 'function') return;  if (typeof el === 'string') {    el = document.querySelector(el);  };  el.addEventListener(event, callback, isCapture);}addEvent(document, 'DOMContentLoaded', () => {  const child = document.querySelector('.child');  const parent = document.querySelector('.parent');  const grandparent = document.querySelector('.grandparent');  addEvent(child, 'click', function (e) {    console.log('child');  });  addEvent(parent, 'click', function (e) {    console.log('parent');  });  addEvent(grandparent, 'click', function (e) {    console.log('grandparent');  });  addEvent(document, 'click', function (e) {    console.log('document');  });  addEvent('html', 'click', function (e) {    console.log('html');  })  addEvent(window, 'click', function (e) {    console.log('window');  })});

addEventListener办法具备第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中产生,如果为true,则事件将在捕捉阶段中产生。 如果单击child元素,它将别离在管制台上打印windowdocumenthtmlgrandparentparent,这就是事件捕捉

9. event.preventDefault() 和 event.stopPropagation()办法之间有什么区别?

event.preventDefault() 办法可避免元素的默认行为。 如果在表单元素中应用,它将阻止其提交。 如果在锚元素中应用,它将阻止其导航。 如果在上下文菜单中应用,它将阻止其显示或显示。 event.stopPropagation()办法用于阻止捕捉和冒泡阶段中以后事件的进一步流传。

10. 如何晓得是否在元素中应用了event.preventDefault()办法?

咱们能够在事件对象中应用event.defaultPrevented属性。 它返回一个布尔值用来表明是否在特定元素中调用了event.preventDefault()

11. 为什么此代码 obj.someprop.x 会引发谬误?

const obj = {};console.log(obj.someprop.x);

显然,因为咱们尝试拜访someprop属性中的x属性,而 someprop 并没有在对象中,所以值为 undefined。 记住对象自身不存在的属性,并且其原型的默认值为undefined。因为undefined没有属性x,所以试图拜访将会报错。

12. 什么是 event.target ?

简略来说,event.target是产生事件的元素或触发事件的元素。

假如有如下的 HTML 构造:

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;border:1px solid red;border-radius:3px;">    <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">        <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">          <button style="margin:10px">             Button          </button>        </div>    </div> </div>

JS 代码如下:

function clickFunc(event) {  console.log(event.target);}

如果单击 button,即便咱们将事件附加在最里面的div上,它也将打印 button 标签,因而咱们能够得出结论event.target是触发事件的元素。

13. 什么是 event.currentTarget??

event.currentTarget是咱们在其上显式附加事件处理程序的元素。

假如有如下的 HTML 构造:

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;border:1px solid red;border-radius:3px;">    <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">        <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">          <button style="margin:10px">             Button          </button>        </div>    </div> </div>

JS 代码如下:

function clickFunc(event) {  console.log(event.currentTarget);}

如果单击 button,即便咱们单击该 button,它也会打印最里面的div标签。 在此示例中,咱们能够得出结论,event.currentTarget是附加事件处理程序的元素。

14. == 和 === 有什么区别?

==用于个别比拟,===用于严格比拟,==在比拟的时候能够转换数据类型,===严格比拟,只有类型不匹配就返回flase

先来看看 == 这兄弟:

强制是将值转换为另一种类型的过程。 在这种状况下,==会执行隐式强制。 在比拟两个值之前,==须要执行一些规定。

假如咱们要比拟x == y的值。

  1. 如果xy的类型雷同,则 JS 会换成===操作符进行比拟。
  2. 如果xnull, yundefined,则返回true
  3. 如果xundefinedynull,则返回true
  4. 如果x的类型是number, y的类型是string,那么返回x == toNumber(y)
  5. 如果x的类型是string, y的类型是number,那么返回toNumber(x) == y
  6. 如果x为类型是boolean,则返回toNumber(x)== y
  7. 如果y为类型是boolean,则返回x == toNumber(y)
  8. 如果xstringsymbolnumber,而yobject类型,则返回x == toPrimitive(y)
  9. 如果xobjectystringsymbol 则返回toPrimitive(x) == y
  10. 剩下的 返回 false

留神:toPrimitive首先在对象中应用valueOf办法,而后应用toString办法来获取该对象的原始值。

举个例子。

xyx == y
55true
1'1'true
nullundefinedtrue
0falsetrue
'1,2'[1,2]true
'[object Object]'{}true

这些例子都返回true

第一个示例合乎条件1,因为xy具备雷同的类型和值。

第二个示例合乎条件4,在比拟之前将y转换为数字。

第三个例子合乎条件2

第四个例子合乎条件7,因为yboolean类型。

第五个示例合乎条件8。 应用toString()办法将数组转换为字符串,该办法返回1,2

最初一个示例合乎条件8。 应用toString()办法将对象转换为字符串,该办法返回[object Object]

xyx === y
55true
1'1'false
nullundefinedfalse
0falsefalse
'1,2'[1,2]false
'[object Object]'{}false

如果应用===运算符,则第一个示例以外的所有比拟将返回false,因为它们的类型不同,而第一个示例将返回true,因为两者的类型和值雷同。

具体更多规定能够对参考我之前的文章:

我对 JS 中相等和全等操作符转化过程始终很蛊惑,直到有了这份算法

15. 为什么在 JS 中比拟两个类似的对象时返回 false?

先看上面的例子:

let a = { a: 1 };let b = { a: 1 };let c = a;console.log(a === b); // 打印 false,即便它们有雷同的属性console.log(a === c); // true

JS 以不同的形式比拟对象和根本类型。在根本类型中,JS 通过值对它们进行比拟,而在对象中,JS 通过援用或存储变量的内存中的地址对它们进行比拟。这就是为什么第一个console.log语句返回false,而第二个console.log语句返回trueac有雷同的援用地址,而ab没有。

16. !! 运算符能做什么?

!!运算符能够将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简略办法。

console.log(!!null); // falseconsole.log(!!undefined); // falseconsole.log(!!''); // falseconsole.log(!!0); // falseconsole.log(!!NaN); // falseconsole.log(!!' '); // trueconsole.log(!!{}); // trueconsole.log(!![]); // trueconsole.log(!!1); // trueconsole.log(!![].length); // false

17. 如何在一行中计算多个表达式的值?

能够应用逗号运算符在一行中计算多个表达式。 它从左到右求值,并返回左边最初一个我的项目或最初一个操作数的值。

let x = 5;x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);function addFive(num) {  return num + 5;}

下面的后果最初失去x的值为27。首先,咱们将x的值减少到6,而后调用函数addFive(6)并将6作为参数传递并将后果重新分配给x,此时x的值为11。之后,将x的以后值乘以2并将其调配给xx的更新值为22。而后,将x的以后值减去5并将后果调配给x x更新后的值为17。最初,咱们将x的值减少10,而后将更新的值调配给x,最终x的值为27

18. 什么是晋升?

晋升是用来形容变量和函数挪动到其(全局或函数)作用域顶部的术语。

为了了解晋升,须要来理解一下执行上下文执行上下文是以后正在执行的“代码环境”。执行上下文有两个阶段:编译执行

编译-在此阶段,JS 推荐获取所有函数申明并将其晋升到其作用域的顶部,以便咱们稍后能够援用它们并获取所有变量申明(应用var关键字进行申明),还会为它们提供默认值: undefined

执行——在这个阶段中,它将值赋给之前晋升的变量,并执行或调用函数(对象中的办法)。

留神:只有应用var申明的变量,或者函数申明才会被晋升,相同,函数表达式或箭头函数,letconst申明的变量,这些都不会被晋升。

假如在全局应用域,有如下的代码:

console.log(y);y = 1;console.log(y);console.log(greet("Mark"));function greet(name){  return 'Hello ' + name + '!';}var y;

下面别离打印:undefined,1, Hello Mark!

下面代码在编译阶段其实是这样的:

function greet(name) {  return 'Hello ' + name + '!';}var y; // 默认值 undefined// 期待“编译”阶段实现,而后开始“执行”阶段/*console.log(y);y = 1;console.log(y);console.log(greet("Mark"));*/

编译阶段实现后,它将启动执行阶段调用办法,并将值调配给变量。

function greet(name) {  return 'Hello ' + name + '!';}var y;//start "execution" phaseconsole.log(y);y = 1;console.log(y);console.log(greet("Mark"));

19. 什么是作用域?

JavaScript 中的作用域是咱们能够无效拜访变量或函数的区域。JS 有三种类型的作用域:全局作用域函数作用域块作用域(ES6)

  • 全局作用域——在全局命名空间中申明的变量或函数位于全局作用域中,因而在代码中的任何中央都能够拜访它们。
//global namespacevar g = "global";function globalFunc(){  function innerFunc(){    console.log(g); // can access "g" because "g" is a global variable  } innerFunc();}  
  • 函数作用域——在函数中申明的变量、函数和参数能够在函数外部拜访,但不能在函数内部拜访。
function myFavoriteFunc(a) {  if (true) {    var b = "Hello " + a;  }  return b;}myFavoriteFunc("World");console.log(a); // Throws a ReferenceError "a" is not definedconsole.log(b); // does not continue here 
  • 块作用域-在块{}中申明的变量(let,const)只能在其中拜访。
 function testBlock(){   if(true){     let z = 5;   }   return z;  } testBlock(); // Throws a ReferenceError "z" is not defined

作用域也是一组用于查找变量的规定。 如果变量在以后作用域中不存在,它将向内部作用域中查找并搜寻,如果该变量不存在,它将再次查找直到达到全局作用域,如果找到,则能够应用它,否则引发谬误,这种查找过程也称为作用域链

   /* 作用域链          外部作用域->内部作用域-> 全局作用域  */  // 全局作用域  var variable1 = "Comrades";     var variable2 = "Sayonara";  function outer(){  // 内部作用域    var variable1 = "World";    function inner(){    // 外部作用域      var variable2 = "Hello";      console.log(variable2 + " " + variable1);    }    inner();  }    outer(); // Hello World

20. 什么是闭包?

这可能是所有问题中最难的一个问题,因为闭包是一个有争议的话题,这里从集体角度来谈谈,如果不妥,多多海涵。

闭包就是一个函数在申明时可能记住以后作用域、父函数作用域、及父函数作用域上的变量和参数的援用,直至通过作用域链上全局作用域,基本上闭包是在申明函数时创立的作用域。

看看小例子:

   // 全局作用域   var globalVar = "abc";   function a(){     console.log(globalVar);   }   a(); // "abc" 

在此示例中,当咱们申明a函数时,全局作用域是a闭包的一部分。

变量globalVar在图中没有值的起因是该变量的值能够依据调用函数a的地位和工夫而扭转。然而在下面的示例中,globalVar变量的值为abc

来看一个更简单的例子:

var globalVar = "global";var outerVar = "outer"function outerFunc(outerParam) {  function innerFunc(innerParam) {    console.log(globalVar, outerParam, innerParam);  }  return innerFunc;}const x = outerFunc(outerVar);outerVar = "outer-2";globalVar = "guess"x("inner");

下面打印后果是 guess outer inner

当咱们调用outerFunc函数并将返回值innerFunc函数调配给变量x时,即便咱们为outerVar变量调配了新值outer-2outerParam也持续保留outer值,因为重新分配是在调用outerFunc之后产生的,并且当咱们调用outerFunc函数时,它会在作用域链中查找outerVar的值,此时的outerVar的值将为 "outer"

当初,当咱们调用援用了innerFuncx变量时,innerParam将具备一个inner值,因为这是咱们在调用中传递的值,而globalVar变量值为guess,因为在调用x变量之前,咱们将一个新值调配给globalVar

上面这个示例演示没有了解好闭包所犯的谬误:

const arrFuncs = [];for(var i = 0; i < 5; i++){  arrFuncs.push(function (){    return i;  });}console.log(i); // i is 5for (let i = 0; i < arrFuncs.length; i++) {  console.log(arrFuncs[i]()); // 都打印 5}

因为闭包,此代码无奈失常运行。var关键字创立一个全局变量,当咱们 push 一个函数时,这里返回的全局变量i。 因而,当咱们在循环后在该数组中调用其中一个函数时,它会打印5,因为咱们失去i的以后值为5,咱们能够拜访它,因为它是全局变量。

因为闭包在创立变量时会保留该变量的援用而不是其值。 咱们能够应用IIFES或应用 let 来代替 var 的申明。

21. JavaScript 中的虚值是什么?

 const falsyValues = ['', 0, null, undefined, NaN, false];

简略的来说虚值就是是在转换为布尔值时变为 false 的值。

22. 如何查看值是否虚值?

应用 Boolean 函数或者 !! 运算符。

23. 'use strict' 是干嘛用的?

"use strict"ES5 个性,它使咱们的代码在函数或整个脚本中处于严格模式严格模式帮忙咱们在代码的晚期防止 bug,并为其增加限度。

严格模式的一些限度:

  1. 变量必须申明后再应用
  2. 函数的参数不能有同名属性,否则报错
  3. 不能应用with语句
  4. 不能对只读属性赋值,否则报错
  5. 不能应用前缀 0 示意八进制数,否则报错
  6. 不能删除不可删除的属性,否则报错
  7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  8. eval不能在它的外层作用域引入变量
  9. evalarguments不能被从新赋值
  10. arguments不会主动反映函数参数的变动
  11. 不能应用arguments.callee
  12. 不能应用arguments.caller
  13. 禁止this指向全局对象
  14. 不能应用fn.callerfn.arguments获取函数调用的堆栈
  15. 减少了保留字(比方protectedstaticinterface

设立”严格模式”的目标,次要有以下几个:

  1. 打消Javascript语法的一些不合理、不谨严之处,缩小一些怪异行为;
  2. 打消代码运行的一些不平安之处,保障代码运行的平安;
  3. 进步编译器效率,减少运行速度;
  4. 为将来新版本的Javascript做好铺垫。

24. JavaScript 中 this 值是什么?

基本上,this指的是以后正在执行或调用该函数的对象的值。this值的变动取决于咱们应用它的上下文和咱们在哪里应用它。

const carDetails = {  name: "Ford Mustang",  yearBought: 2005,  getName(){    return this.name;  },  isRegistered: true};console.log(carDetails.getName()); // Ford Mustang

这通常是咱们冀望后果的,因为在getName办法中咱们返回this.name,在此上下文中,this指向的是carDetails对象,该对象以后是执行函数的“所有者”对象。

接下咱们做些奇怪的事件:

var name = "Ford Ranger";var getCarName = carDetails.getName;console.log(getCarName()); // Ford Ranger

下面打印Ford Ranger,这很奇怪,因为在第一个console.log语句中打印的是Ford Mustang。这样做的起因是getCarName办法有一个不同的“所有者”对象,即window对象。在全局作用域中应用var关键字申明变量会在window对象中附加与变量名称雷同的属性。请记住,当没有应用“use strict”时,在全局作用域中this指的是window对象。

console.log(getCarName === window.getCarName); // trueconsole.log(getCarName === this.getCarName); // true

本例中的thiswindow援用同一个对象。

解决这个问题的一种办法是在函数中应用applycall办法。

console.log(getCarName.apply(carDetails)); // Ford Mustangconsole.log(getCarName.call(carDetails));  // Ford Mustang

applycall办法冀望第一个参数是一个对象,该对象是函数外部this的值。

IIFE立刻执行的函数表达式,在全局作用域内申明的函数,对象外部办法中的匿名函数和外部函数的this具备默认值,该值指向window对象。

   (function (){     console.log(this);   })(); // 打印 "window" 对象   function iHateThis(){      console.log(this);   }   iHateThis(); // 打印 "window" 对象   const myFavoriteObj = {     guessThis(){        function getName(){          console.log(this.name);        }        getName();     },     name: 'Marko Polo',     thisIsAnnoying(callback){       callback();     }   };   myFavoriteObj.guessThis(); // 打印 "window" 对象   myFavoriteObj.thisIsAnnoying(function (){     console.log(this); // 打印 "window" 对象   });

如果咱们要获取myFavoriteObj对象中的name属性(即Marko Polo)的值,则有两种办法能够解决此问题。

一种是将 this 值保留在变量中。

const myFavoriteObj = { guessThis(){  const self = this; // 把 this 值保留在 self 变量中  function getName(){    console.log(self.name);  }  getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){   callback();  }};

第二种形式是应用箭头函数

const myFavoriteObj = {  guessThis(){     const getName = () => {        console.log(this.name);     }     getName();  },  name: 'Marko Polo',  thisIsAnnoying(callback){   callback();  }};

箭头函数没有本人的 this。它复制了这个关闭的词法作用域中this值,在这个例子中,this值在getName外部函数之外,也就是myFavoriteObj对象。

25. 对象的 prototype(原型) 是什么?

简略地说,原型就是对象的蓝图。如果它存在以后对象中,则将其用作属性和办法的回退。它是在对象之间共享属性和性能的办法,这也是JavaScript实现继承的外围。

const o = {};console.log(o.toString()); // logs [object Object] 

即便o对象中不存在o.toString办法,它也不会引发谬误,而是返回字符串[object Object]。 当对象中不存在属性时,它将查看其原型,如果依然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具备雷同属性的属性为止。 原型链的开端是Object.prototype

console.log(o.toString === Object.prototype.toString); // logs true

26. 什么是 IIFE,它的用处是什么?

IIFE或立刻调用的函数表达式是在创立或申明后将被调用或执行的函数。 创立IIFE的语法是,将function (){}包裹在在括号()内,而后再用另一个括号()调用它,如:(function(){})()

(function(){  ...} ());(function () {  ...})();(function named(params) {  ...})();(() => {});(function (global) {  ...})(window);const utility = (function () {  return {    ...  }})

这些示例都是无效的IIFE。 倒数第二个救命表明咱们能够将参数传递给IIFE函数。 最初一个示例表明,咱们能够将IIFE的后果保留到变量中,以便稍后应用。

IIFE的一个次要作用是防止与全局作用域内的其余变量命名抵触或净化全局命名空间,来个例子。

<script src="https://cdnurl.com/somelibrary.js"></script>

假如咱们引入了一个omelibr.js的链接,它提供了一些咱们在代码中应用的全局函数,然而这个库有两个办法咱们没有应用:createGraphdrawGraph,因为这些办法都有bug。咱们想实现本人的createGraphdrawGraph办法。

解决此问题的一种办法是间接笼罩:

<script src="https://cdnurl.com/somelibrary.js"></script><script>   function createGraph() {      // createGraph logic here   }   function drawGraph() {      // drawGraph logic here   }</script>

当咱们应用这个解决方案时,咱们笼罩了库提供给咱们的那两个办法。

另一种形式是咱们本人改名称:

<script src="https://cdnurl.com/somelibrary.js"></script><script>   function myCreateGraph() {      // createGraph logic here   }   function myDrawGraph() {      // drawGraph logic here   }</script>

当咱们应用这个解决方案时,咱们把那些函数调用更改为新的函数名。

还有一种办法就是应用IIFE

<script src="https://cdnurl.com/somelibrary.js"></script><script>   const graphUtility = (function () {      function createGraph() {         // createGraph logic here      }      function drawGraph() {         // drawGraph logic here      }      return {         createGraph,         drawGraph      }   })</script>

在此解决方案中,咱们要申明了graphUtility 变量,用来保留IIFE执行的后果,该函数返回一个蕴含两个办法createGraphdrawGraph的对象。

IIFE 还能够用来解决一个常见的面试题:

var li = document.querySelectorAll('.list-group > li');for (var i = 0, len = li.length; i < len; i++) {   li[i].addEventListener('click', function (e) {      console.log(i);   })

假如咱们有一个带有list-group类的ul元素,它有5li子元素。 当咱们单击单个li元素时,打印对应的下标值。但在此外上述代码不起作用,这里每次点击 li 打印 i 的值都是5,这是因为闭包的起因。

闭包只是函数记住其以后作用域,父函数作用域和全局作用域的变量援用的能力。 当咱们在全局作用域内应用var关键字申明变量时,就创立全局变量i。 因而,当咱们单击li元素时,它将打印5,因为这是稍后在回调函数中援用它时i的值。

应用 IIFE 能够解决此问题:

var li = document.querySelectorAll('.list-group > li');for (var i = 0, len = li.length; i < len; i++) {   (function (currentIndex) {      li[currentIndex].addEventListener('click', function (e) {         console.log(currentIndex);      })   })(i);}

该解决方案之所以行的通,是因为IIFE会为每次迭代创立一个新的作用域,咱们捕捉i的值并将其传递给currentIndex参数,因而调用IIFE时,每次迭代的currentIndex值都是不同的。

27. Function.prototype.apply 办法的用处是什么?

apply() 办法调用一个具备给定this值的函数,以及作为一个数组(或相似数组对象)提供的参数。

const details = {  message: 'Hello World!'};function getMessage(){  return this.message;}getMessage.apply(details); // 'Hello World!'
call()办法的作用和 apply() 办法相似,区别就是call()办法承受的是参数列表,而apply()办法承受的是一个参数数组。
const person = {  name: "Marko Polo"};function greeting(greetingMessage) {  return `${greetingMessage} ${this.name}`;}greeting.apply(person, ['Hello']); // "Hello Marko Polo!"

28. Function.prototype.call 办法的用处是什么?

call() 办法应用一个指定的 this 值和独自给出的一个或多个参数来调用一个函数。

const details = {  message: 'Hello World!'};function getMessage(){  return this.message;}getMessage.call(details); // 'Hello World!'

留神:该办法的语法和作用与 apply() 办法相似,只有一个区别,就是 call() 办法承受的是一个参数列表,而 apply() 办法承受的是一个蕴含多个参数的数组。

const person = {  name: "Marko Polo"};function greeting(greetingMessage) {  return `${greetingMessage} ${this.name}`;}greeting.call(person, 'Hello'); // "Hello Marko Polo!"

29. Function.prototype.apply 和 Function.prototype.call 之间有什么区别?

apply()办法能够在应用一个指定的 this 值和一个参数数组(或类数组对象)的前提下调用某个函数或办法。call()办法相似于apply(),不同之处仅仅是call()承受的参数是参数列表。

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]); // 15reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15

30. Function.prototype.bind 的用处是什么?

bind() 办法创立一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时应用。

import React from 'react';class MyComponent extends React.Component {     constructor(props){          super(props);           this.state = {             value : ""          }            this.handleChange = this.handleChange.bind(this);           // 将 “handleChange” 办法绑定到 “MyComponent” 组件     }     handleChange(e){       //do something amazing here     }     render(){        return (              <>                <input type={this.props.type}                        value={this.state.value}                     onChange={this.handleChange}                                        />              </>        )     }}

31. 什么是函数式编程? JavaScript 的哪些个性使其成为函数式语言的候选语言?

函数式编程(通常缩写为FP)是通过编写纯函数,防止共享状态、可变数据、副作用 来构建软件的过程。数式编程是申明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程造成比照,面向对象中应用程序的状态通常与对象中的办法共享和共处。

函数式编程是一种编程范式 ,这意味着它是一种基于一些根本的定义准则(如上所列)思考软件构建的形式。当然,编程范示的其余示例也包含面向对象编程和过程编程。

函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但如果不相熟它以及与之相干的常见模式,函数式的代码也可能看起来更密集芜杂,并且 相干文献对新人来说是不好了解的。

JavaScript反对闭包和高阶函数是函数式编程语言的特点。

32. 什么是高阶函数?

高阶函数只是将函数作为参数或返回值的函数。

function higherOrderFunction(param,callback){    return callback(param);}

33. 为什么函数被称为一等公民?

在JavaScript中,函数不仅领有所有传统函数的应用形式(申明和调用),而且能够做到像简略值一样赋值(var func = function(){})、传参(function func(x,callback){callback();})、返回(function(){return function(){}}),这样的函数也称之为第一级函数(First-class Function)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)。这样的多重身份让JavaScript的函数变得十分重要。

34. 手动实现 Array.prototype.map 办法

map() 办法创立一个新数组,其后果是该数组中的每个元素都调用一个提供的函数后返回的后果。

function map(arr, mapCallback) {  // 首先,查看传递的参数是否正确。  if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {     return [];  } else {    let result = [];    // 每次调用此函数时,咱们都会创立一个 result 数组    // 因为咱们不想扭转原始数组。    for (let i = 0, len = arr.length; i < len; i++) {      result.push(mapCallback(arr[i], i, arr));       // 将 mapCallback 返回的后果 push 到 result 数组中    }    return result;  }}

35. 手动实现Array.prototype.filter办法

filter() 办法创立一个新数组, 其蕴含通过所提供函数实现的测试的所有元素。

function filter(arr, filterCallback) {  // 首先,查看传递的参数是否正确。  if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')   {    return [];  } else {    let result = [];     // 每次调用此函数时,咱们都会创立一个 result 数组     // 因为咱们不想扭转原始数组。    for (let i = 0, len = arr.length; i < len; i++) {      // 查看 filterCallback 的返回值是否是真值      if (filterCallback(arr[i], i, arr)) {       // 如果条件为真,则将数组元素 push 到 result 中        result.push(arr[i]);      }    }    return result; // return the result array  }}

36. 手动实现Array.prototype.reduce办法

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

function reduce(arr, reduceCallback, initialValue) {  // 首先,查看传递的参数是否正确。  if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')   {    return [];  } else {    // 如果没有将initialValue传递给该函数,咱们将应用第一个数组项作为initialValue    let hasInitialValue = initialValue !== undefined;    let value = hasInitialValue ? initialValue : arr[0];   、    // 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始    for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {      value = reduceCallback(value, arr[i], i, arr);     }    return value;  }}

37. arguments 的对象是什么?

arguments对象是函数中传递的参数值的汇合。它是一个相似数组的对象,因为它有一个length属性,咱们能够应用数组索引表示法arguments[1]来拜访单个值,但它没有数组中的内置办法,如:forEachreducefiltermap

咱们能够应用Array.prototype.slicearguments对象转换成一个数组。

function one() {  return Array.prototype.slice.call(arguments);}

留神:箭头函数中没有arguments对象。

function one() {  return arguments;}const two = function () {  return arguments;}const three = function three() {  return arguments;}const four = () => arguments;four(); // Throws an error  - arguments is not defined

当咱们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。应用rest语法,能够解决这个问题。

const four = (...args) => args;

这会主动将所有参数值放入数组中。

38. 如何创立一个没有 prototype(原型)的对象?

咱们能够应用Object.create办法创立没有原型的对象。

const o1 = {};console.log(o1.toString()); // [object Object]const o2 = Object.create(null);console.log(o2.toString());// throws an error o2.toString is not a function 

39. 为什么在调用这个函数时,代码中的b会变成一个全局变量?

function myFunc() {  let a = b = 0;}myFunc();

起因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符呈现在一个表达式中时,它们是从右向左求值的。所以下面代码变成了这样:

function myFunc() {  let a = (b = 0);}myFunc();

首先,表达式b = 0求值,在本例中b没有申明。因而,JS引擎在这个函数外创立了一个全局变量b,之后表达式b = 0的返回值为0,并赋给新的局部变量a

咱们能够通过在赋值之前先申明变量来解决这个问题。

function myFunc() {  let a,b;  a = b = 0;}myFunc();

40. ECMAScript 是什么?

ECMAScript 是编写脚本语言的规范,这意味着JavaScript遵循ECMAScript规范中的标准变动,因为它是JavaScript的蓝图。

ECMAScript 和 Javascript,实质上都跟一门语言无关,一个是语言自身的名字,一个是语言的约束条件
只不过创造JavaScript的那个人(Netscape公司),把货色交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的规范,因为过后有java语言了,又想强调这个货色是让ECMA这个人定的规定,所以就这样一个神奇的货色诞生了,这个货色的名称就叫做ECMAScript。

javaScript = ECMAScript + DOM + BOM(自认为是一种狭义的JavaScript)

ECMAScript说什么JavaScript就得做什么!

JavaScript(广义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!

——忽然感觉JavaScript好没有尊严,为啥要搞集体进去束缚本人,

那个人被发明进去也好冤屈,本人被发明进去齐全是因为要束缚JavaScript。

因为篇幅过长,我将此系列分成高低二篇,下篇咱们在见。


代码部署后可能存在的BUG没法实时晓得,预先为了解决这些BUG,花了大量的工夫进行log 调试,这边顺便给大家举荐一个好用的BUG监控工具 Fundebug。

原文:https://dev.to/macmacky/70-ja...


交换

文章每周继续更新,能够微信搜寻「 大迁世界 」第一工夫浏览和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq44924588... 曾经收录,整顿了很多我的文档,欢送Star和欠缺,大家面试能够参照考点温习,另外关注公众号,后盾回复福利,即可看到福利,你懂的。