共计 22839 个字符,预计需要花费 58 分钟才能阅读完成。
作者: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 有什么区别?
在了解 undefined
和null
之间的差别之前,咱们先来看看它们的类似类。
它们属于 JavaScript 的 7 种根本类型。
let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
它们是属于虚值,能够应用 Boolean(value)
或!!value
将其转换为布尔值时,值为false
。
console.log(!!null); // false
console.log(!!undefined); // false
console.log(Boolean(null)); // false
console.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); // undefined
console.log(doNothing()); // undefined
console.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);
});
在比拟 null
和undefined
时,咱们应用 ==
时失去 true
,应用===
时失去false
:
console.log(null == undefined); // true
console.log(null === undefined); // false
2. && 运算符能做什么
&&
也能够叫 逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到任何虚值表达式,则返回最初一个真值表达式。它采纳短路来避免不必要的工作。
console.log(false && 1 && []); // false
console.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); // 1
function 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
。
事件流传有三个阶段:
- 捕捉阶段–事件从
window
开始,而后向下到每个元素,直到达到指标元素。 - 指标阶段–事件已达到目标元素。
- 冒泡阶段–事件从指标元素冒泡,而后回升到每个元素,直到达到
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
元素,它将别离在管制台上记录 child
,parent
,grandparent
,html
,document
和window
,这就是事件冒泡。
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
元素,它将别离在管制台上打印 window
,document
,html
,grandparent
和parent
,这就是 事件捕捉。
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
的值。
- 如果
x
和y
的类型雷同,则 JS 会换成===
操作符进行比拟。 - 如果
x
为null
,y
为undefined
,则返回true
。 - 如果
x
为undefined
且y
为null
,则返回true
。 - 如果
x
的类型是number
,y
的类型是string
,那么返回x == toNumber(y)
。 - 如果
x
的类型是string
,y
的类型是number
,那么返回toNumber(x) == y
。 - 如果
x
为类型是boolean
,则返回toNumber(x)== y
。 - 如果
y
为类型是boolean
,则返回x == toNumber(y)
。 - 如果
x
是string
、symbol
或number
,而y
是object
类型,则返回x == toPrimitive(y)
。 - 如果
x
是object
,y
是string
,symbol
则返回toPrimitive(x) == y
。 - 剩下的 返回
false
留神:toPrimitive
首先在对象中应用 valueOf
办法,而后应用 toString
办法来获取该对象的原始值。
举个例子。
x | y | x == y |
---|---|---|
5 | 5 | true |
1 | ‘1’ | true |
null | undefined | true |
0 | false | true |
‘1,2’ | [1,2] | true |
‘[object Object]’ | {} | true |
这些例子都返回true
。
第一个示例合乎 条件 1
,因为 x
和y
具备雷同的类型和值。
第二个示例合乎 条件 4
,在比拟之前将 y
转换为数字。
第三个例子合乎 条件 2
。
第四个例子合乎 条件 7
,因为 y
是boolean
类型。
第五个示例合乎 条件 8
。应用 toString()
办法将数组转换为字符串,该办法返回1,2
。
最初一个示例合乎 条件 8
。应用 toString()
办法将对象转换为字符串,该办法返回[object Object]
。
x | y | x === y |
---|---|---|
5 | 5 | true |
1 | ‘1’ | false |
null | undefined | false |
0 | false | false |
‘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
语句返回 true
。a
和c
有雷同的援用地址,而 a
和b
没有。
16. !! 运算符能做什么?
!!
运算符能够将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简略办法。
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!''); // false
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!' '); // true
console.log(!!{}); // true
console.log(!![]); // true
console.log(!!1); // true
console.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
并将其调配给 x
,x
的更新值为 22
。而后,将x
的以后值减去 5
并将后果调配给 x
x
更新后的值为 17
。最初,咱们将x
的值减少 10
,而后将更新的值调配给x
,最终x
的值为27
。
18. 什么是晋升?
晋升 是用来形容变量和函数挪动到其 (全局或函数) 作用域顶部的术语。
为了了解晋升,须要来理解一下 执行上下文 。 执行上下文 是以后正在执行的 “代码环境”。执行上下文有两个阶段: 编译
和执行
。
编译 - 在此阶段,JS 推荐获取所有 函数申明 并将其 晋升 到其作用域的顶部,以便咱们稍后能够援用它们并获取所有变量申明(应用 var
关键字进行申明),还会为它们提供默认值:undefined
。
执行——在这个阶段中,它将值赋给之前晋升的变量,并执行或调用函数(对象中的办法)。
留神:只有应用 var
申明的变量,或者函数申明才会被晋升,相同,函数表达式或箭头函数,let
和 const
申明的变量,这些都不会被晋升。
假如在全局应用域,有如下的代码:
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" phase
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
19. 什么是作用域?
JavaScript 中的作用域是咱们能够无效拜访变量或函数的区域。JS 有三种类型的作用域:全局作用域 、 函数作用域 和块作用域(ES6)。
- 全局作用域——在全局命名空间中申明的变量或函数位于全局作用域中,因而在代码中的任何中央都能够拜访它们。
//global namespace
var 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 defined
console.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-2
,outerParam
也持续保留 outer
值,因为重新分配是在调用 outerFunc
之后产生的,并且当咱们调用 outerFunc
函数时,它会在作用域链中查找 outerVar
的值,此时的 outerVar
的值将为 "outer"
。
当初,当咱们调用援用了 innerFunc
的x
变量时,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 5
for (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,并为其增加限度。
严格模式 的一些限度:
- 变量必须申明后再应用
- 函数的参数不能有同名属性,否则报错
- 不能应用
with
语句 - 不能对只读属性赋值,否则报错
- 不能应用前缀 0 示意八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete global[prop]
eval
不能在它的外层作用域引入变量eval
和arguments
不能被从新赋值arguments
不会主动反映函数参数的变动- 不能应用
arguments.callee
- 不能应用
arguments.caller
- 禁止
this
指向全局对象 - 不能应用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 减少了保留字(比方
protected
、static
和interface
)
设立”严格模式”的目标,次要有以下几个:
- 打消 Javascript 语法的一些不合理、不谨严之处,缩小一些怪异行为;
- 打消代码运行的一些不平安之处,保障代码运行的平安;
- 进步编译器效率,减少运行速度;
- 为将来新版本的 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); // true
console.log(getCarName === this.getCarName); // true
本例中的 this
和window
援用同一个对象。
解决这个问题的一种办法是在函数中应用 apply
和call
办法。
console.log(getCarName.apply(carDetails)); // Ford Mustang
console.log(getCarName.call(carDetails)); // Ford Mustang
apply
和 call
办法冀望第一个参数是一个对象,该对象是函数外部 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
的链接,它提供了一些咱们在代码中应用的全局函数,然而这个库有两个办法咱们没有应用:createGraph
和 drawGraph
,因为这些办法都有bug
。咱们想实现本人的createGraph
和drawGraph
办法。
解决此问题的一种办法是间接笼罩:
<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 执行的后果,该函数返回一个蕴含两个办法 createGraph
和drawGraph
的对象。
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
元素,它有 5
个li
子元素。当咱们单击单个 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]); // 15
reduceAdd.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]
来拜访单个值,但它没有数组中的内置办法,如:forEach
、reduce
、filter
和map
。
咱们能够应用 Array.prototype.slice
将arguments
对象转换成一个数组。
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 和欠缺,大家面试能够参照考点温习,另外关注公众号,后盾回复福利,即可看到福利,你懂的。