JavaScript 面试问题及答案70 个 JavaScript 面试问题
大家好,祝大家有美好的一天,新年快乐🎆🎆🎆!
这篇很长,所以请耐心等待一秒钟甚至一个小时。每个问题的每个答案中都有一个向上箭头↑ 的链接,可以让你返回问题列表,这样你就不用浪费时间上下滚动了。
问题
undefined
1.和有什么区别null
?- 2. &&运算符的作用是什么?
- 3.|| 运算符起什么作用?
- 4. 使用 + 或一元加号运算符是将字符串转换为数字的最快方法吗?
- 5.什么是DOM?
- 6.什么是事件传播?
- 7.什么是事件冒泡?
- 8.什么是事件捕获?
event.preventDefault()
9.和方法有什么区别event.stopPropagation()
?- 10.如何知道
event.preventDefault()
元素中是否使用了该方法? - 11.为什么这段代码 obj.someprop.x 会抛出错误?
- 12. 什么是
event.target
? - 13. 什么是
event.currentTarget
? ==
14.和有什么区别===
?- 15. 为什么在 JavaScript 中比较两个相似的对象时会返回 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
方法是什么? Function.prototype.apply
29.和有什么区别Function.prototype.call
?- 30. 的用法是什么
Function.prototype.bind
? - 31. 什么是函数式编程?JavaScript 的哪些特性使其成为函数式语言的候选?
- 32.什么是高阶函数?
- 33. 为什么函数被称为“一等对象”?
- 34.
Array.prototype.map
手动实现该方法。 - 35.
Array.prototype.filter
手动实现该方法。 - 36.
Array.prototype.reduce
手动实现该方法。 - 37.物体是什么
arguments
? - 38.如何创建没有原型的对象?
- 39.为什么
b
在这段代码中调用这个函数时会变成全局变量? - 40.什么是ECMAScript?
- 41. ES6或ECMAScript 2015有哪些新特性?
var
42. 、let
和关键字之间有什么区别const
?- 43.什么是箭头函数?
- 44.什么是类?
- 45.什么是模板字符串?
- 46.什么是对象解构?
- 47. 什么是
ES6 Modules
? - 48. 对象是什么
Set
以及它是如何工作的? - 49.什么是回调函数?
- 50.什么是Promise?
- 51.什么是async/await以及它是如何工作的?
- 52.Spread运算符和Rest运算符有什么区别?
- 53.什么是默认参数?
- 54.什么是包装对象?
- 55.隐式强制转换和显式强制转换有什么区别?
- 56.什么是
NaN
?以及如何检查一个值是否是NaN
? - 57.如何检查一个值是否是数组?
- 58. 如何在不使用
%
或模运算符的情况下检查一个数字是否为偶数? - 59.如何检查对象中是否存在某个属性?
- 60.什么是AJAX?
- 61. JavaScript 中创建对象的方式有哪些?
Object.seal
62.和方法有什么区别Object.freeze
?in
63.对象中的运算符和方法有什么区别hasOwnProperty
?- 64. JavasScript 中处理异步代码的方法有哪些?
- 65.函数表达式和函数声明有什么区别?
- 66. 函数有多少种调用方式?
- 67.什么是记忆化(memoization)以及它有什么用处?
- 68. 实现记忆辅助函数。
- 69. 为什么要
typeof null
返回object
?如何检查一个值是否为null
? - 70.
new
关键字起什么作用?
undefined
1.和有什么区别null
?
↑undefined
在了解和之间的差异之前,null
我们必须了解它们之间的相似之处。
- 它们属于JavaScript 的7 种原始类型。
let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
- 它们是假
Boolean(value)
值。使用或将其转换为布尔值时,计算结果为 false 的值!!value
。
console.log(!!null); //logs false
console.log(!!undefined); //logs false
console.log(Boolean(null)); //logs false
console.log(Boolean(undefined)); //logs false
好的,让我们来谈谈区别。
undefined
是尚未赋值变量的默认值。或者是一个没有明确返回值的函数,例如……console.log(1)
。又或者是一个对象中不存在的属性。JavaScript 引擎会为我们完成赋值undefined
。
let _thisIsUndefined;
const doNothing = () => {};
const someObj = {
a : "ay",
b : "bee",
c : "si"
};
console.log(_thisIsUndefined); //logs undefined
console.log(doNothing()); //logs undefined
console.log(someObj["d"]); //logs undefined
null
是“表示无值的值”。null
是已明确定义为变量的值。在此示例中,当方法未引发错误null
时,我们会获得 的值。fs.readFile
fs.readFile('path/to/file', (e,data) => {
console.log(e); //it logs null when no error occurred
if(e){
console.log(e);
}
console.log(data);
});
当比较和时null
,undefined
我们得到true
使用时==
和false
使用时。您可以在此处===
阅读原因。
console.log(null == undefined); // logs true
console.log(null === undefined); // logs false
2. 操作员做什么&&
?
↑逻辑与运算&&
符“或”在其操作数中找到第一个假值表达式并返回,如果没有找到,则返回最后一个表达式。它使用短路来避免不必要的工作。我曾在一个项目中关闭数据库连接时在代码块中使用过它。catch
console.log(false && 1 && []); //logs false
console.log(" " && true && 5); //logs 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. 操作员做什么||
?
↑逻辑||
或运算符or在其操作数中找到第一个真值表达式并返回。它也使用了短路来避免不必要的运算。在ES6支持默认函数参数之前,它曾用于在函数中初始化默认参数值。
console.log(null || 1 || undefined); //logs 1
function logName(name) {
var n = name || "Mark";
console.log(n);
}
logName(); //logs "Mark"
4. 使用+或一元加号运算符是将字符串转换为数字的最快方法吗?
↑根据MDN 文档,这+
是将字符串转换为数字的最快方法,因为如果值已经是数字,它就不会对值执行任何操作。
5.什么是DOM?
↑ DOM代表文档对象模型(Document Object Model),是HTML 和 XML 文档的接口 ( API )。当浏览器首次读取(解析)我们的 HTML 文档时,它会创建一个基于 HTML 文档的大型对象,这个大型对象就是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>
JavaScriptdocument
中的对象代表DOM 。它为我们提供了许多方法,可用于选择元素、更新元素内容等等。
6.什么是事件传播?
↑当事件发生在DOM元素上时,该事件并不会完全发生在该元素上。在冒泡阶段,事件会向上冒泡,即到达其父元素、祖父元素、祖父元素的父元素,直到到达 。而window
在捕获阶段,事件会从 开始window
向下到达触发事件的元素event.target
。
事件传播分为三个阶段。
7.什么是事件冒泡?
↑当事件发生在DOM元素上时,该事件并不会完全发生在该元素上。在冒泡阶段,事件会向上冒泡,依次到达其父元素、祖父元素、祖父元素的父元素,直到到达window
。
如果我们有一个这样的示例标记。
<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元素上时,该事件并不会完全发生在该元素上。在捕获阶段,事件会从 DOM 元素开始,window
一直向下到达触发该事件的元素。
如果我们有一个这样的示例标记。
<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');
}, true);
addEvent(parent, 'click', function (e) {
console.log('parent');
}, true);
addEvent(grandparent, 'click', function (e) {
console.log('grandparent');
}, true);
addEvent(document, 'click', function (e) {
console.log('document');
}, true);
addEvent('html', 'click', function (e) {
console.log('html');
}, true)
addEvent(window, 'click', function (e) {
console.log('window');
}, true)
});
该addEventListener
方法有第三个可选参数useCapture,其默认值为:如果false
事件发生在捕获阶段,则true
该事件将在冒泡阶段发生。如果我们点击元素,child
它会在控制台上分别打印window
、document
、和。这就是事件捕获。html
grandparent
parent
child
event.preventDefault()
9.和方法有什么区别event.stopPropagation()
?
↑该event.preventDefault()
方法阻止元素的默认行为。如果在form
元素中使用,则会阻止元素提交。如果在anchor
元素中使用,则会阻止元素导航。如果在元素中使用,contextmenu
则会阻止元素显示。该event.stopPropagation()
方法还可以阻止事件的传播,或者阻止事件在冒泡或捕获阶段发生。
10.如何知道event.preventDefault()
元素中是否使用了该方法?
↑event.defaultPrevented
我们可以在事件对象中使用属性。它返回一个值boolean
,指示是否event.preventDefault()
在特定元素中调用了该属性。
11.为什么此代码会obj.someprop.x
抛出错误?
const obj = {};
console.log(obj.someprop.x);
↑显然,这会抛出一个错误,因为我们试图访问属性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>
示例 JavaScript。
function clickFunc(event) {
console.log(event.target);
}
如果您单击按钮,它将记录按钮标记,即使我们将事件附加在最外层,div
它也始终会记录按钮,因此我们可以得出结论,event.target是触发事件的元素。
13.什么是event.currentTarget?
↑ event.currentTarget是我们明确附加事件处理程序的元素。
复制问题 12中的标记。
示例 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);
}
如果您点击该按钮,即使我们点击了它,它也会记录最外层的div标记。在此示例中,我们可以得出结论, event.currentTarget是我们附加事件处理程序的元素。
==
14.和有什么区别===
?
↑ ==
(抽象相等)和===
(严格相等)的区别在于,前者在强制转换后==
按值比较,而后者不经过强制转换,按值和类型比较。===
让我们深入探讨一下==
。首先我们来谈谈强制转换。
强制转换是将一个值转换为另一种类型的过程。在本例中,==
进行了隐式强制转换。==
在比较两个值之前, 需要执行一些条件。
假设我们必须比较x == y
值。
- 如果
x
和y
类型相同,则使用===
运算符进行比较。 - 如果
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
且x
是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
。
第一个例子符合条件一,因为x
和y
具有相同的类型和值。
第二个例子进入条件四 y
,在比较之前转换为number
。
第三个例子符合第二个条件。
第四个例子符合条件七,因为y
是boolean
。
第五个示例进入条件八string
。使用toString()
返回的方法将数组转换为1,2
。
最后一个例子是条件十string
。使用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
因为两者具有相同的类型和值。
15. 为什么在 JavaScript 中比较两个相似的对象时会返回false ?
↑假设我们有下面一个例子。
let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // logs false even though they have the same property
console.log(a === c); // logs true hmm
JavaScript比较对象和原始类型的方式有所不同。原始类型通过值比较,而对象则通过引用或变量在内存中存储的地址比较。这就是为什么第一个console.log
语句返回false
,而第二个console.log
语句返回true
。a
和c
具有相同的引用,a
而 和 则b
不同。
16. !!运算符起什么作用?
↑双重非运算符或!!将右侧的值强制转换为布尔值。基本上,这是一种将值转换为布尔值的奇特方法。
console.log(!!null); //logs false
console.log(!!undefined); //logs false
console.log(!!''); //logs false
console.log(!!0); //logs false
console.log(!!NaN); //logs false
console.log(!!' '); //logs true
console.log(!!{}); //logs true
console.log(!![]); //logs true
console.log(!!1); //logs true
console.log(!![].length); //logs 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
更新后的值,即17。最后,我们将 的值增加x
10,并将更新后的值分配给x
现在的 值,x
即27。
18.什么是提升?
↑ 提升是用于描述将变量和函数移动到我们定义该变量或函数的(全局或函数)范围顶部的术语。
为了理解代码提升,我必须解释一下执行上下文。执行上下文
是当前正在执行的“代码环境”。执行上下文有两个阶段:编译和执行。
编译- 在此阶段,它获取所有函数声明并将它们提升到其范围的顶部,以便我们稍后可以引用它们,并获取所有变量声明 (使用 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; //implicit "undefined" assignment
//waiting for "compilation" phase to finish
//then start "execution" phase
/*
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 中的作用域是指我们可以有效访问变量或函数的区域。JavaScript 有三种作用域:全局作用域、函数作用域和块作用域(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
作用域也是一组用于查找变量的规则。如果变量在当前作用域中不存在,它会在外部作用域中查找,如果仍然不存在,它会再次查找,直到到达全局作用域。如果变量存在,则可以使用它;如果不存在,则会抛出错误。它会搜索最近的变量,一旦找到,就会停止搜索或查找。这称为作用域链。
/* Scope Chain
Inside inner function perspective
inner's scope -> outer's scope -> global's scope
*/
//Global Scope
var variable1 = "Comrades";
var variable2 = "Sayonara";
function outer(){
//outer's scope
var variable1 = "World";
function inner(){
//inner's scope
var variable2 = "Hello";
console.log(variable2 + " " + variable1);
}
inner();
}
outer();
// logs Hello World
// because (variable2 = "Hello") and (variable1 = "World") are the nearest
// variables inside inner's scope.
20.什么是闭包?
↑这可能是所有问题中最难的一个,因为闭包本身就是一个有争议的话题。所以我会根据我的理解来解释一下。
闭包简单来说就是函数在声明时,通过作用域链记住其当前作用域、父函数作用域、父函数的父函数作用域直至全局作用域中变量和参数的引用的能力。本质上,它是函数声明时创建的作用域。
例子是解释闭包的好方法。
//Global's Scope
var globalVar = "abc";
function a(){
//testClosures's Scope
console.log(globalVar);
}
a(); //logs "abc"
/* Scope Chain
Inside a function perspective
a's scope -> global's scope
*/
在这个例子中,当我们声明a
函数时,全局范围是a's
闭包的一部分。
图中变量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
,即使我们为变量赋了新值outer-2,outerParam
的值仍然是outer,因为 重新赋值发生在函数调用之后,而此时我们调用函数时会在作用域链中查找 的值, 的值是“outer”。现在,当我们调用引用 的变量时, 的值将是inner,因为这是我们在调用时传递的值,而变量的值将是guess ,因为在调用变量之前我们为 赋了一个新值,而在调用时,作用域链中的 的值是guess。outerVar
outer
outerFunc
outerVar
outerVar
x
innerFunc
innerParam
globalVar
x
globalVar
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]()); // all logs "5"
}
由于闭包的原因,这段代码无法按预期工作。
关键字var
创建一个全局变量,当我们推送一个函数时,
我们返回这个全局变量i
。因此,当我们在循环后调用该数组中的某个函数时,它会记录日志,5
因为我们获得了它
的当前值,并且我们可以访问它,因为它是一个全局变量。这是因为闭包在创建时保留的是该变量的引用,而不是它的值。我们可以使用IIFES或将关键字更改为 来实现块作用域来解决这个问题。i
5
var
let
21. JavaScript中的假值是什么?
const falsyValues = ['', 0, null, undefined, NaN, false];
falsy值是转换为布尔值后变为false 的值。
22.如何检查一个值是否为假?
23. 做什么"use strict"
?
↑是 JavaScript"use strict"
中的 ES5 特性,它使我们的代码在函数或整个脚本中处于严格模式。严格模式帮助我们在代码早期避免错误,并为其添加限制。
严格模式给我们的限制。
- 分配或访问未声明的变量。
function returnY(){
"use strict";
y = 123;
return y;
}
- 为只读或不可写的全局变量赋值;
"use strict";
var NaN = NaN;
var undefined = undefined;
var Infinity = "and beyond";
- 删除不可删除的属性。
"use strict";
const obj = {};
Object.defineProperty(obj, 'x', {
value : '1'
});
delete obj.x;
- 参数名称重复。
"use strict";
function someFunc(a, b, b, c){
}
- 使用eval函数创建变量。
"use strict";
eval("var x = 1;");
console.log(x); //Throws a Reference Error x is not defined
- 其默认值为
undefined
。
"use strict";
function showMeThis(){
return this;
}
showMeThis(); //returns undefined
严格模式中的限制远不止这些。
24. JavaScript 中的值是什么this
?
↑基本上,this
指的是当前正在执行或调用函数的对象的值。我说“当前”是因为this的值会根据我们使用它的上下文和位置而变化。
const carDetails = {
name: "Ford Mustang",
yearBought: 2005,
getName(){
return this.name;
},
isRegistered: true
};
console.log(carDetails.getName()); // logs Ford Mustang
这正是我们通常所期望的,因为在getName方法中我们返回this.name
,this
在这个上下文中指的是carDetails
当前正在执行的函数的“所有者”对象的对象。
好的,让我们添加一些代码让它变得奇怪一些。在console.log
语句下面添加以下三行代码
var name = "Ford Ranger";
var getCarName = carDetails.getName;
console.log(getCarName()); // logs Ford Ranger
第二条console.log
语句打印出了单词Ford Ranger,这很奇怪,因为在第一个console.log
语句中打印的是Ford Mustang。这是因为该getCarName
方法有一个不同的“所有者”对象,即对象本身。在全局范围内window
使用关键字 声明变量会将对象中与变量同名的属性附加到全局范围内。记住,在全局范围内,当不使用 时,指的是对象本身。var
window
this
window
"use strict"
console.log(getCarName === window.getCarName); //logs true
console.log(getCarName === this.getCarName); // logs true
this
在window
本例中指的是同一个对象。
解决这个问题的一种方法是使用函数中的apply
和call
方法。
console.log(getCarName.apply(carDetails)); //logs Ford Mustang
console.log(getCarName.call(carDetails)); //logs Ford Mustang
和方法期望第一个参数是一个对象,该对象将是该函数内部的apply
值。call
this
IIFE或立即调用函数表达式,在全局范围内声明的函数、匿名函数和对象内部方法中的内部函数都默认具有指向窗口对象的this。
(function (){
console.log(this);
})(); //logs the "window" object
function iHateThis(){
console.log(this);
}
iHateThis(); //logs the "window" object
const myFavoriteObj = {
guessThis(){
function getThis(){
console.log(this);
}
getThis();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
myFavoriteObj.guessThis(); //logs the "window" object
myFavoriteObj.thisIsAnnoying(function (){
console.log(this); //logs the "window" object
});
如果我们想获取对象中Marko Poloname
属性的值,有两种方法可以解决这个问题。myFavoriteObj
首先,我们将的值保存this
在一个变量中。
const myFavoriteObj = {
guessThis(){
const self = this; //saves the this value to the "self" variable
function getName(){
console.log(self.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
this
在这张图中,我们保存了对象的值myFavoriteObj
。因此我们可以在内部函数中访问它getName
。
其次,我们使用ES6箭头函数。
const myFavoriteObj = {
guessThis(){
const getName = () => {
//copies the value of "this" outside of this arrow function
console.log(this.name);
}
getName();
},
name: 'Marko Polo',
thisIsAnnoying(callback){
callback();
}
};
箭头函数没有自己的this
。它会复制封闭词法作用域的 的值,或者在本例中,复制内部函数外部(即对象)this
的值。我们还可以根据函数的调用方式来确定 的值。this
getName
myFavoriteObj
this
25.prototype
物体的“是什么”?
↑prototype
简单来说,A是一个对象的蓝图。如果当前对象中存在 A,它将作为属性和方法的后备。A 是对象之间共享属性和功能的方式。它是 JavaScript原型继承的核心概念。
const o = {};
console.log(o.toString()); // logs [object Object]
即使该o.toString
方法在对象中不存在,o
它也不会抛出错误,而是返回一个字符串[object Object]
。当对象中不存在某个属性时,它会查找其原型,如果仍然不存在,它会查找原型的原型,依此类推,直到在原型链中找到具有相同属性的属性。原型链的末尾在Object.prototypenull
之后。
console.log(o.toString === Object.prototype.toString); // logs true
// which means we we're looking up the Prototype Chain and it reached
// the Object.prototype and used the "toString" method.
26.什么是IIFE,它有什么用?
↑ IIFE (立即调用函数表达式)是指在创建或声明后立即调用或执行的函数。创建 IIFE 的语法是:将 括function (){}
在括号()
或分组运算符中,将函数视为表达式,然后用另一个括号 调用它()
。IIFE如下所示。(function(){})()
(function () {
}());
(function () {
})();
(function named(params) {
})();
(() => {
})();
(function (global) {
})(window);
const utility = (function () {
return {
//utilities
};
})();
这些例子都是有效的IIFE 函数。倒数第二个例子表明我们可以将参数传递给IIFE函数。最后一个例子表明我们可以将IIFE的结果保存到变量中,以便以后引用。
IIFE的最佳用途是实现初始化设置功能,并避免与全局范围内的其他变量发生命名冲突或污染全局命名空间。我们来看一个例子。
<script src="https://cdnurl.com/somelibrary.js"></script>
假设我们有一个指向某个库的链接somelibrary.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>
在这个解决方案中,我们创建了一个实用变量,它是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-groupul
类的元素,它有 5个子元素。我们想要在单击单个元素时获取的值。 但我们想要的代码中的行为不起作用。相反,它会记录对元素的任何单击。我们遇到的问题是由于闭包的工作方式造成的。闭包只是函数记住其当前作用域、其父函数作用域和全局作用域中的变量引用的能力。当我们在全局作用域中使用关键字声明变量时,显然我们正在创建一个全局变量。因此,当我们单击一个元素时,它会记录5 ,因为这是我们稍后在回调函数中引用它时的值。li
console.log
i
li
5
li
var
i
li
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
并将其传递给参数,因此当我们调用IIFE时,每次迭代currentIndex
的值都是不同的。currentIndex
27.使用Function.prototype.apply
方法是什么?
↑调用apply
一个函数,指定this
调用时该函数的“所有者”对象。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.apply(details); // returns 'Hello World!'
此方法的工作原理与此类似,Function.prototype.call
唯一的区别在于我们传递参数的方式。apply
我们将参数作为数组传递。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.apply(person, ['Hello']); // returns "Hello Marko Polo!"
28.使用Function.prototype.call
方法是什么?
↑调用call
一个函数,指定this
调用时该函数的“所有者”对象。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.call(details); // returns 'Hello World!'
此方法的工作原理与此类似,Function.prototype.apply
唯一的区别在于传递参数的方式。call
我们直接传递参数,并用逗号分隔,
每个参数。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.call(person, 'Hello'); // returns "Hello Marko Polo!"
Function.prototype.apply
29.和有什么区别Function.prototype.call
?
↑apply
和之间的唯一区别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]); // returns 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15
30. 的用法是什么Function.prototype.bind
?
↑该方法返回一个绑定 到特定值或“所有者”对象bind
的新函数,以便我们稍后在代码中使用它。,方法会立即调用该函数,而不是像 方法那样返回一个新函数。 this
call
apply
bind
import React from 'react';
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {
value : ""
}
this.handleChange = this.handleChange.bind(this);
// Binds the "handleChange" method to the "MyComponent" component
}
handleChange(e){
//do something amazing here
}
render(){
return (
<>
<input type={this.props.type}
value={this.state.value}
onChange={this.handleChange}
/>
</>
)
}
}
31.什么是函数式编程? JavaScript的哪些特性使它成为函数式语言的候选?
↑ 函数式编程是一种声明式编程范式或模式,它说明了我们如何使用表达式通过函数构建应用程序,该表达式计算一个值,而不会改变或更改传递给它的参数。
JavaScript数组具有map、filter、reduce方法,它们是函数式编程世界中最著名的函数,因为它们很有用,并且它们不会变异或更改数组,这使得这些函数变得纯粹,并且 JavaScript 支持闭包和高阶函数,这是函数式编程语言的一个特征。
- map方法创建一个新数组,并对数组中的每个元素调用提供的回调函数。
const words = ["Functional", "Procedural", "Object-Oriented"];
const wordsLength = words.map(word => word.length);
- filter方法创建一个新数组,其中包含所有通过回调函数测试的元素。
const data = [
{ name: 'Mark', isRegistered: true },
{ name: 'Mary', isRegistered: false },
{ name: 'Mae', isRegistered: true }
];
const registeredUsers = data.filter(user => user.isRegistered);
- Reduce方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值。
const strs = ["I", " ", "am", " ", "Iron", " ", "Man"];
const result = strs.reduce((acc, currentStr) => acc + currentStr, "");
32.什么是高阶函数?
↑ 高阶函数是可以返回一个函数或接收一个或多个具有函数值的参数的函数。
function higherOrderFunction(param,callback){
return callback(param);
}
33.为什么函数被称为“一等对象”?
↑ JavaScript 中的函数是“一等对象”,因为它们在 JavaScript 中被视为与任何其他值一样。它们可以赋值给变量,可以作为对象的属性(称为方法),可以作为数组中的元素,可以作为参数传递给函数,也可以作为函数的返回值。函数与JavaScript中任何其他值的唯一区别在于,函数可以被调用(invoked)。
34.Array.prototype.map
手动实现该方法。
function map(arr, mapCallback) {
// First, we check if the parameters passed are right.
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
let result = [];
// We're making a results array every time we call this function
// because we don't want to mutate the original array.
for (let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i], i, arr));
// push the result of the mapCallback in the 'result' array
}
return result; // return the result array
}
}
正如该方法的MDN描述Array.prototype.map
。
map() 方法通过对调用数组中的每个元素调用提供的函数来创建一个新数组。
35.Array.prototype.filter
手动实现该方法。
function filter(arr, filterCallback) {
// First, we check if the parameters passed are right.
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// We're making a results array every time we call this function
// because we don't want to mutate the original array.
for (let i = 0, len = arr.length; i < len; i++) {
// check if the return value of the filterCallback is true or "truthy"
if (filterCallback(arr[i], i, arr)) {
// push the current item in the 'result' array if the condition is true
result.push(arr[i]);
}
}
return result; // return the result array
}
}
正如该方法的MDN描述Array.prototype.filter
。
filter() 方法创建一个新数组,其中包含通过所提供函数实现的测试的所有元素。
36.Array.prototype.reduce
手动实现该方法。
function reduce(arr, reduceCallback, initialValue) {
// First, we check if the parameters passed are right.
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// If no initialValue has been passed to the function we're gonna use the
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
// first array item as the initialValue
// Then we're gonna start looping at index 1 if there is no
// initialValue has been passed to the function else we start at 0 if
// there is an initialValue.
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
// Then for every iteration we assign the result of the
// reduceCallback to the variable value.
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
正如该方法的MDN描述Array.prototype.reduce
。
reduce() 方法对数组的每个元素执行一个 reducer 函数(由您提供),从而生成单个输出值。
37.什么是arguments对象?
↑参数对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它具有length属性,并且我们可以使用数组索引符号访问单个值,但它不具备数组中的内置方法、和。 它可以帮助我们知道函数中传递的参数数量。arguments[1]
forEach
reduce
filter
map
我们可以arguments
使用将对象转换为数组Array.prototype.slice
。
function one() {
return Array.prototype.slice.call(arguments);
}
注意:该arguments
对象不适用于 ES6 箭头函数。
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
它会抛出一个错误。如果您的环境支持rest 语法,ReferenceError: arguments is not defined
我们可以解决这个问题。
const four = (...args) => args;
这会自动将所有参数值放入数组中。
38.如何创建没有原型的对象?
↑我们可以使用该方法创建一个没有原型的对象Object.create
。
const o1 = {};
console.log(o1.toString());
// logs [object Object] get this method to the Object.prototype
const o2 = Object.create(null);
// the first parameter is the prototype of the object "o2" which in this
// case will be null specifying we don't want any prototype
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
通过let
关键字将其赋值给这个新的局部变量。
我们可以通过先声明变量然后再赋值来解决这个问题。
function myFunc() {
let a,b;
a = b = 0;
}
myFunc();
40.什么是ECMAScript?
↑ ECMAScript是制定脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
41. ES6或ECMAScript 2015有哪些新特性?
var
42. 、let
和关键字之间有什么区别const
?
↑var
使用关键字声明的变量具有函数作用域。
这意味着即使我们在代码块内声明变量,也可以跨函数访问该变量。
function giveMeX(showX) {
if (showX) {
var x = 5;
}
return x;
}
console.log(giveMeX(false));
console.log(giveMeX(true));
第一条console.log
语句打印了日志undefined
,第二条语句打印5
了日志。 由于x
变量被提升到了函数作用域的顶部,所以我们可以访问它。所以我们的函数代码是这样解释的。
function giveMeX(showX) {
var x; // has a default value of undefined
if (showX) {
x = 5;
}
return x;
}
如果您想知道为什么它会undefined
在第一个console.log
语句中记录,请记住声明的没有初始值的变量具有默认值undefined
。
let
使用和关键字声明的变量const
是块作用域的。这意味着变量只能在{}
声明它的块内访问。
function giveMeX(showX) {
if (showX) {
let x = 5;
}
return x;
}
function giveMeY(showY) {
if (showY) {
let y = 5;
}
return y;
}
如果我们使用 的参数调用此函数,false
它会抛出一个异常,Reference Error
因为我们无法访问该块之外的x
和y
变量,并且这些变量不会被提升。
和 之间也有区别let
,我们const
可以使用 赋值,let
但不能在 中赋值const
,而是const
可变的。这意味着,如果我们赋给const
一个对象,我们可以更改其属性的值,但不能为该变量重新赋值。
43.什么是箭头函数?
↑ 箭头函数是 JavaScript 中一种创建函数的新方法。箭头函数创建函数所需的时间较少,并且语法比函数表达式更简洁,因为我们省略了function
关键字。
//ES5 Version
var getCurrentDate = function (){
return new Date();
}
//ES6 Version
const getCurrentDate = () => new Date();
在此示例中,ES5 版本分别需要function(){}
声明和return
关键字来创建函数和返回值。在箭头函数版本中,我们只需要()
括号,而不需要return
语句,因为如果只有一个表达式或值要返回,箭头函数会隐式返回。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;
我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果箭头函数中只有一个参数,则可以省略括号,这也是有效的。
const getArgs = () => arguments
const getArgs2 = (...rest) => rest
箭头函数无法访问arguments
对象。因此调用第一个getArgs
函数会抛出错误。我们可以使用剩余参数来获取箭头函数中传递的所有参数。
const data = {
result: 0,
nums: [1, 2, 3, 4, 5],
computeResult() {
// "this" here refers to the "data" object
const addAll = () => {
// arrow functions "copies" the "this" value of
// the lexical enclosing function
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};
箭头函数没有自己的this
值。它捕获或获取this
词法封闭函数的值,或者在本例中,addAll
函数复制方法this
的值computeResult
。如果我们在全局范围内声明一个箭头函数,那么它的值this
就是window
对象。
44.什么是类?
↑ 类是JavaScript中编写构造函数的新方式。它是使用构造函数的语法糖,其底层仍然使用原型和基于原型的继承。
//ES5 Version
function Person(firstName, lastName, age, address){
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
Person.self = function(){
return this;
}
Person.prototype.toString = function(){
return "[object Person]";
}
Person.prototype.getFullName = function (){
return this.firstName + " " + this.lastName;
}
//ES6 Version
class Person {
constructor(firstName, lastName, age, address){
this.lastName = lastName;
this.firstName = firstName;
this.age = age;
this.address = address;
}
static self() {
return this;
}
toString(){
return "[object Person]";
}
getFullName(){
return `${this.firstName} ${this.lastName}`;
}
}
覆盖方法并从另一个类继承。
//ES5 Version
Employee.prototype = Object.create(Person.prototype);
function Employee(firstName, lastName, age, address, jobTitle, yearStarted) {
Person.call(this, firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
Employee.prototype.describe = function () {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}
Employee.prototype.toString = function () {
return "[object Employee]";
}
//ES6 Version
class Employee extends Person { //Inherits from "Person" class
constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;
}
describe() {
return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}
toString() { // Overriding the "toString" method of "Person"
return "[object Employee]";
}
}
那么我们如何知道它在底层使用了原型呢?
class Something {
}
function AnotherSomething(){
}
const as = new AnotherSomething();
const s = new Something();
console.log(typeof Something); // logs "function"
console.log(typeof AnotherSomething); // logs "function"
console.log(as.toString()); // logs "[object Object]"
console.log(as.toString()); // logs "[object Object]"
console.log(as.toString === Object.prototype.toString);
console.log(s.toString === Object.prototype.toString);
// both logs return true indicating that we are still using
// prototypes under the hoods because the Object.prototype is
// the last part of the Prototype Chain and "Something"
// and "AnotherSomething" both inherit from Object.prototype
45.什么是模板字符串?
↑ 模板字面量是JavaScript 中创建字符串的一种新方式。我们可以使用反引号或反引号来创建模板字面量。
//ES5 Version
var greet = 'Hi I\'m Mark';
//ES6 Version
let greet = `Hi I'm Mark`;
在 ES5 版本中,我们需要使用 来'
转义 ,\
以逃避该符号的正常功能,在本例中,该功能是结束该字符串值。在模板字面量中,我们不需要这样做。
//ES5 Version
var lastWords = '\n'
+ ' I \n'
+ ' Am \n'
+ 'Iron Man \n';
//ES6 Version
let lastWords = `
I
Am
Iron Man
`;
在 ES5 版本中,我们需要添加此代码\n
才能在字符串中添加新行。在模板字符串中,我们不需要这样做。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
const greet = name => {
return `Hello ${name} !`;
}
在 ES5 版本中,如果我们需要在字符串中添加表达式或值,则需要使用+
或 字符串连接运算符。在模板字面量中,我们可以使用 嵌入表达式,${expr}
这使得它比 ES5 版本更简洁。
46.什么是对象解构?
↑ 对象解构是一种从对象或数组中获取或提取值的全新且更简洁的方法。
假设我们有一个看起来像这样的物体。
const employee = {
firstName: "Marko",
lastName: "Polo",
position: "Software Developer",
yearHired: 2017
};
从对象中获取属性的传统方法是创建一个与对象属性同名的变量。这种方法很麻烦,因为我们要为每个属性创建一个新的变量。想象一下,我们有一个包含大量属性和方法的大对象,用这种方式提取属性会非常麻烦。
var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;
如果我们使用对象解构,它看起来会比以前更简洁,也更省时。对象解构的语法是:如果要获取对象中的属性,我们使用 and {}
,并在其中指定要提取的属性;如果要从数组中获取数据,我们使用[]
。
let { firstName, lastName, position, yearHired } = employee;
如果我们想更改要提取的变量名称,可以使用以下propertyName:newName
语法。在此示例中,变量的值fName
将保存属性的值firstName
,lName
变量将保存属性的值lastName
。
let { firstName: fName, lastName: lName, position, yearHired } = employee;
解构时也可以设置默认值。在本例中,如果对象中的firstName
属性持有一个值,那么解构时该变量将持有默认值。undefined
firstName
"Mark"
let { firstName = "Mark", lastName: lName, position, yearHired } = employee;
47. 什么是ES6 Modules
?
↑ 模块让我们能够将代码库拆分成多个文件,以提高可维护性,这样我们就可以避免将所有代码都放在一个大文件中(呸)。在 ES6 支持模块之前, JavaScript中有两种流行的模块系统用于提高代码可维护性。
- CommonJS - Node.js
- AMD(异步模块定义)-浏览器
基本上,使用模块的语法很简单,import
用于从另一个文件或多个功能或值中获取export
功能,同时用于从一个文件或多个功能或值中公开功能。
在文件或命名导出中导出功能
使用 ES5(CommonJS)
// Using ES5 CommonJS - helpers.js
exports.isNull = function (val) {
return val === null;
}
exports.isUndefined = function (val) {
return val === undefined;
}
exports.isNullOrUndefined = function (val) {
return exports.isNull(val) || exports.isUndefined(val);
}
使用 ES6 模块
// Using ES6 Modules - helpers.js
export function isNull(val){
return val === null;
}
export function isUndefined(val) {
return val === undefined;
}
export function isNullOrUndefined(val) {
return isNull(val) || isUndefined(val);
}
在另一个文件中导入功能
// Using ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;
// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');
// ES6 Modules - index.js
import * as helpers from './helpers.js'; // helpers is an object
// or
import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';
// using "as" for renaming named exports
导出文件或默认导出中的单个功能
使用 ES5(CommonJS)
// Using ES5 (CommonJS) - index.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
module.exports = Helpers;
使用 ES6 模块
// Using ES6 Modules - helpers.js
class Helpers {
static isNull(val) {
return val === null;
}
static isUndefined(val) {
return val === undefined;
}
static isNullOrUndefined(val) {
return this.isNull(val) || this.isUndefined(val);
}
}
export default Helpers
从另一个文件导入单个功能
使用 ES5(CommonJS)
// Using ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js');
console.log(Helpers.isNull(null));
使用 ES6 模块
import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));
这是使用ES6 模块的基础知识。我不会详细解释所有关于模块的内容,因为这是一个很宽泛的话题,而且我的帖子已经很长了。
48. 对象是什么Set
以及它是如何工作的?
↑ Set对象是ES6 的一项功能,可用于存储唯一值、原始值或对象引用。Set 中的值只能出现一次。它使用SameValueZero算法检查集合对象中是否存在某个值。
我们可以Set
使用构造函数创建实例Set
,并且可以选择传递一个Iterable
作为初始值。
const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);
Set
我们可以使用该方法向实例添加新值add
,由于该方法add
返回Set
对象,因此可以进行链式add
调用。如果对象中已存在该值,Set
则不会再次添加。
set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// the last "k" will not be added to the set object because it already exists
Set
我们可以使用该方法从实例中删除一个值delete
,该方法返回一个boolean
指示,指示对象true
中是否存在该值Set
,并false
指示该值不存在。
set2.delete("k") // returns true because "k" exists in the set object
set2.delete("z") // returns false because "z" does not exists in the set object
Set
我们可以使用该方法检查实例中是否存在特定值has
。
set2.has("a") // returns true because "a" exists in the set object
set2.has("z") // returns false because "z" does not exists in the set object
Set
我们可以使用该属性获取实例的长度size
。
set2.size // returns 10
Set
我们可以使用 删除或移除实例中的所有元素clear
。
set2.clear(); // clears the set data
我们可以使用该Set
对象删除数组中的重复元素。
const numbers = [1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 5];
const uniqueNums = [...new Set(numbers)]; // has a value of [1,2,3,4,5,6,7,8]
49.什么是回调函数?
↑回调函数是在稍后时间点调用的函数。
const btnAdd = document.getElementById('btnAdd');
btnAdd.addEventListener('click', function clickCallback(e) {
// do something useless
});
在此示例中,我们等待id 为btnAddclick event
的元素中的,如果是,则执行该函数。回调函数将某些功能添加到某些数据或事件中。Array 中的、和方法需要回调作为参数。回调的一个很好的类比是,当您打电话给某人,如果他们没有接听,您会留言,并希望他们回调。打电话给某人或留言的行为是事件或数据,而回调是您期望稍后发生的操作。clicked
clickCallback
reduce
filter
map
50.什么是Promise?
↑ Promises是JavaScript中处理异步操作的一种方式。它表示异步操作的值。Promises是为了解决在我们使用回调函数之前执行和处理异步代码的问题而创建的。
fs.readFile('somefile.txt', function (e, data) {
if (e) {
console.log(e);
}
console.log(data);
});
这种方法的问题在于,如果我们在回调函数内部再执行一个异步操作,代码就会变得混乱且难以阅读。这种代码被称为“回调地狱”。
//Callback Hell yucksss
fs.readFile('somefile.txt', function (e, data) {
//your code here
fs.readdir('directory', function (e, files) {
//your code here
fs.mkdir('directory', function (e) {
//your code here
})
})
})
如果我们在此代码中使用承诺,它将更具可读性、易于理解和易于维护。
promReadFile('file/path')
.then(data => {
return promReaddir('directory');
})
.then(data => {
return promMkdir('directory');
})
.catch(e => {
console.log(e);
})
承诺有 3 种不同的状态。
待处理- Promise 的初始状态。由于操作尚未完成,因此 Promise 的结果尚不清楚。
已完成- 异步操作已完成并成功返回结果值。
已拒绝- 异步操作失败,并且有失败的原因。
已解决- 如果承诺已被履行或拒绝。
Promise构造函数有两个参数,分别是函数resolve
和reject
。
如果异步操作已完成且没有错误,则调用该resolve
函数来解析 Promise;如果发生错误,
则调用该reject
函数并将错误或原因传递给它。
我们可以使用该方法访问已完成 Promise 的结果.then
,并在该方法中捕获错误.catch
。我们在该方法中链接多个异步 Promise 操作,.then
因为该.then
方法返回一个Promise,就像上图中的示例一样。
const myPromiseAsync = (...args) => {
return new Promise((resolve, reject) => {
doSomeAsync(...args, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
})
})
}
myPromiseAsync()
.then(result => {
console.log(result);
})
.catch(reason => {
console.log(reason);
})
我们可以创建一个辅助函数,将带有回调的异步操作转换为 Promise。它的工作原理类似于Node 核心模块中的promisifyutil
实用函数。
const toPromise = (asyncFuncWithCallback) => {
return (...args) => {
return new Promise((res, rej) => {
asyncFuncWithCallback(...args, (e, result) => {
return e ? rej(e) : res(result);
});
});
}
}
const promReadFile = toPromise(fs.readFile);
promReadFile('file/path')
.then((data) => {
console.log(data);
})
.catch(e => console.log(e));
51.什么是async/await以及它是如何工作的?
↑ async/await是JavaScript中编写异步或非阻塞代码的新方法。它建立在Promises之上。它使编写的异步代码比
Promises和Callbacks更具可读性和简洁性。但是在使用此功能之前,您必须学习Promises的基础知识,因为正如我之前所说,它建立在Promises之上,这意味着它仍然在底层使用Promises 。
使用承诺。
function callApi() {
return fetch("url/to/api/endpoint")
.then(resp => resp.json())
.then(data => {
//do something with "data"
}).catch(err => {
//do something with "err"
});
}
使用 Async/Await。
注意:我们使用旧的try/catch语句来捕获try语句内任何异步操作中发生的任何错误。
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"
}
}
注意:函数声明前的async关键字使函数隐式返回一个Promise。
const giveMeOne = async () => 1;
giveMeOne()
.then((num) => {
console.log(num); // logs 1
});
注意:await关键字只能在异步函数中使用。在任何其他非异步函数中使用await关键字都会引发错误。await关键字等待右侧表达式(可能是Promise)返回后再执行下一行代码。
const giveMeOne = async () => 1;
function getOne() {
try {
const num = await giveMeOne();
console.log(num);
} catch (e) {
console.log(e);
}
}
//Throws a Compile-Time Error = Uncaught SyntaxError: await is only valid in an async function
async function getTwo() {
try {
const num1 = await giveMeOne(); //finishes this async operation first before going to
const num2 = await giveMeOne(); //this line
return num1 + num2;
} catch (e) {
console.log(e);
}
}
await getTwo(); // returns 2
52.Spread运算符和Rest运算符有什么区别?
↑ Spread运算符和Rest 参数具有相同的运算符,...
区别在于,Spread 运算符我们将数组中的单个数据赋予或传播到另一个数据,而Rest 参数用于函数或数组中以获取所有参数或值并将它们放入数组中或提取其中的一些部分。
function add(a, b) {
return a + b;
};
const nums = [5, 6];
const sum = add(...nums);
console.log(sum);
在这个例子中,我们在调用函数时使用了展开运算符来展开add
数组。因此,参数 的值为5 ,的值为6。因此和为11。nums
a
b
function add(...rest) {
return rest.reduce((total,current) => total + current);
};
console.log(add(1, 2)); // logs 3
console.log(add(1, 2, 3, 4, 5)); // logs 15
在这个例子中,我们有一个函数add
,它接受任意数量的参数并将它们全部加起来并返回总数。
const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); //logs 1
console.log(others); //logs [2,3,4,5]
在另一个例子中,我们使用Rest 运算符提取所有剩余的数组值并将它们放入数组中others
(第一项除外)。
53.什么是默认参数?
↑ 默认参数是在JavaScript中定义默认变量的新方法,它在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;
}
//If we don't pass any argument for 'a' or 'b' then
// it's gonna use the "default parameter" value which is 0
add(1); // returns 1
我们还可以在默认参数中使用解构。
function getFirst([first, ...rest] = [0, 1]) {
return first;
}
getFirst(); // returns 0
getFirst([10,20,30]); // returns 10
function getArr({ nums } = { nums: [1, 2, 3, 4] }){
return nums;
}
getArr(); // returns [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // returns [5,4,3,2,1]
我们还可以将先定义的参数用于后定义的参数。
function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
callback();
}
doSomethingWithValue(); //logs "Hello World"
54.什么是包装对象?
↑ 原始值,如string
、number
和boolean
(除了和null
),undefined
尽管不是,但也具有属性和方法objects
。
let name = "marko";
console.log(typeof name); // logs "string"
console.log(name.toUpperCase()); // logs "MARKO"
name
是一个primitive string
没有属性和方法的值,但在这个例子中,我们调用一个toUpperCase()
不会引发错误但返回的方法MARKO
。
这样做的原因是,该值primitive
被临时转换或强制转换为,object
因此name
变量的行为类似于object
。primitive
除了null
和之外,每个undefined
都有包装器对象。包装器对象包括String
、Number
、和。在这种情况下,调用在后台看起来像这样。Boolean
Symbol
BigInt
name.toUpperCase()
console.log(new String(name).toUpperCase()); // logs "MARKO"
当我们访问完属性或调用完方法后,新创建的对象会被立即丢弃。
55.隐式强制转换和显式强制转换有什么区别?
↑ 隐式强制转换是一种将值转换为另一种类型的方法,无需程序员直接或手动执行。
假设我们有下面一个例子。
console.log(1 + '6');
console.log(false + true);
console.log(6 * '2');
第一条 语句console.log
输出16
。在其他语言中,这会引发编译时错误,但在JavaScript中,被1
转换为 ,string
然后与+
运算符连接。我们什么也没做,但JavaScript会自动为我们进行转换。
第二条 console.log
语句输出1
,它将 转换为 ,false
结果boolean
为0
,true
为1
,因此结果为1
。第三条
语句输出,它将 转换为 ,然后再进行乘法运算,因此结果为。JavaScript强制转换规则 console.log
12
'2'
number
6 * 2
12
而显式强制则是将值转换为另一种类型的方式,我们(程序员)明确地这样做。
console.log(1 + parseInt('6'));
在这个例子中,我们使用parseInt
函数将转换为'6'
,number
然后添加1
并6
使用+
运算符。
56.什么是NaN
?以及如何检查一个值是否是NaN
?
↑ NaN
表示“非数字” ,是JavaScript中的一个值,其结果是将数字转换为或执行运算后变为非数字值,从而得到结果NaN
。
let a;
console.log(parseInt('abc'));
console.log(parseInt(null));
console.log(parseInt(undefined));
console.log(parseInt(++a));
console.log(parseInt({} * 10));
console.log(parseInt('abc' - 2));
console.log(parseInt(0 / 0));
console.log(parseInt('10a' * 10));
JavaScript有一个内置方法isNaN
可以测试值是否为isNaN
值。但这个函数的行为很奇怪。
console.log(isNaN()); //logs true
console.log(isNaN(undefined)); //logs true
console.log(isNaN({})); //logs true
console.log(isNaN(String('a'))); //logs true
console.log(isNaN(() => { })); //logs true
即使我们传递的值不是,所有这些console.log
语句都会返回。true
NaN
在ES6或ECMAScript 2015中,建议我们使用Number.isNaN
方法,因为它确实会检查值是否确实存在NaN
,或者我们可以创建自己的辅助函数来检查这个问题,因为在JavaScript NaN
中,这是唯一不等于其自身的值。
function checkIfNaN(value) {
return value !== value;
}
57.如何检查一个值是否是数组?
↑我们可以使用Array全局对象中的方法检查一个值是否为数组。如果传递给它的参数是数组,则返回 true ,否则返回 false。Array.isArray
console.log(Array.isArray(5)); //logs false
console.log(Array.isArray("")); //logs false
console.log(Array.isArray()); //logs false
console.log(Array.isArray(null)); //logs false
console.log(Array.isArray({ length: 5 })); //logs false
console.log(Array.isArray([])); //logs true
如果您的环境不支持此方法,您可以使用 polyfill 实现。
function isArray(value){
return Object.prototype.toString.call(value) === "[object Array]"
}
58. 如何在不使用%
或模运算符的情况下检查一个数字是否为偶数?
↑我们可以使用按位与&
运算符来解决这个问题。它对&
操作数进行运算,并将它们视为二进制值,然后执行与运算。
function isEven(num) {
if (num & 1) {
return false;
} else {
return true;
}
};
0
二进制为000。二进制1
为001。二进制2
为010。3
二进制为011。二进制为100。二进制为101。二进制为110。二进制 为111。等等...4
5
6
7
a |
b |
a & b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
所以当我们执行console.log
这个表达式时,5 & 1
它返回1
。好的,首先,&
运算符将两个数字都转换为二进制,因此5
变为101和1
001 。然后,它使用按位与运算符 比较每一位(0 和 1)。101 001。从表中我们可以看出,只有当AND 相等时,结果才会是。
&
1
a
b
1
101 & 001 |
---|
101 |
001 |
001 |
- 因此我们首先比较最左边的
1
&
0
结果应该是多少0
。 - 然后我们比较中间位的
0
&
0
结果应该是多少0
。 - 然后我们比较最后一位的
1
&
1
结果应该是多少1
。 - 然后将二进制结果
001
转换为十进制数1
。
如果我们用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);
}
59.如何检查对象中是否存在某个属性?
↑有三种方法可以检查对象中是否存在某个属性。
首先,使用in
运算符。使用运算符的语法in
如下。如果属性存在,则propertyname in object
返回,否则返回。true
false
const o = {
"prop" : "bwahahah",
"prop2" : "hweasa"
};
console.log("prop" in o); //This logs true indicating the property "prop" is in "o" object
console.log("prop1" in o); //This logs false indicating the property "prop" is not in "o" object
其次,使用hasOwnProperty
对象中的方法。JavaScript 中的所有对象都支持此方法。true
如果属性存在,则返回 ,否则返回false
。
//Still using the o object in the first example.
console.log(o.hasOwnProperty("prop2")); // This logs true
console.log(o.hasOwnProperty("prop1")); // This logs false
第三,使用括号表示法obj["prop"]
。如果属性存在,则返回该属性的值,否则返回undefined
。
//Still using the o object in the first example.
console.log(o["prop"]); // This logs "bwahahah"
console.log(o["prop1"]); // This logs undefined
60.什么是AJAX?
↑ AJAX代表异步 JavaScript 和 XML。它是一组用于异步显示数据的相关技术。这意味着我们可以在不重新加载网页的情况下将数据发送到服务器并从服务器获取数据。
用于AJAX的技术。
- HTML——网页结构
- CSS——网页样式
- JavaScript——网页的行为和DOM的更新
- XMLHttpRequest API - 用于从服务器发送和检索数据
- PHP、Python、Nodejs - 一些服务器端语言
61. JavaScript 中创建对象的方式有哪些?
↑使用对象字面量。
const o = {
name: "Mark",
greeting() {
return `Hi, I'm ${this.name}`;
}
};
o.greeting(); //returns "Hi, I'm Mark"
使用构造函数。
function Person(name) {
this.name = name;
}
Person.prototype.greeting = function () {
return `Hi, I'm ${this.name}`;
}
const mark = new Person("Mark");
mark.greeting(); //returns "Hi, I'm Mark"
使用Object.create方法。
const n = {
greeting() {
return `Hi, I'm ${this.name}`;
}
};
const o = Object.create(n); // sets the prototype of "o" to be "n"
o.name = "Mark";
console.log(o.greeting()); // logs "Hi, I'm Mark"
Object.seal
62.和方法有什么区别Object.freeze
?
↑这两种方法的区别在于,当我们Object.freeze
对一个对象使用该方法时,该对象的属性是不可变的,这意味着我们无法更改或编辑这些属性的值。而在该Object.seal
方法中,我们可以更改这些现有属性,但不能向对象添加新属性。
in
63.对象中的运算符和方法有什么区别hasOwnProperty
?
↑众所周知,这两个函数都会检查对象中是否存在某个属性。返回true
false。它们的区别在于,如果在当前对象中未找到该属性,in
运算符还会检查对象的原型链,hasOwnProperty
而该方法仅检查当前对象中是否存在该属性,而忽略原型链。
// We'll still use the object in the previous question.
console.log("prop" in o); // This logs true;
console.log("toString" in o); // This logs true, the toString method is available in this object's prototype which is the Object.prototype
console.log(o.hasOwnProperty("prop")); // This logs true
console.log(o.hasOwnProperty("toString")); // This logs false, does not check the object's prototype
64. JavasScript 中处理异步代码的方法有哪些?
65.函数表达式和函数声明有什么区别?
↑假设我们有下面一个例子。
hoistedFunc();
notHoistedFunc();
function hoistedFunc(){
console.log("I am hoisted");
}
var notHoistedFunc = function(){
console.log("I will not be hoisted!");
}
调用notHoistedFunc
会抛出错误,而hoistedFunc
调用不会,因为hoistedFunc
被提升了,而 却notHoistedFunc
没有。
点击此处了解提升 。
66. 函数有多少种调用方式?
↑ JavaScript中有四种调用函数的方式。调用方式决定了该函数的值或“所有者”对象。this
- 作为函数调用- 如果函数不是作为方法、构造函数或方法调用,
apply
则call
它将作为函数调用。该函数的“所有者”对象将是window
对象。
//Global Scope
function add(a,b){
console.log(this);
return a + b;
}
add(1,5); // logs the "window" object and returns 6
const o = {
method(callback){
callback();
}
}
o.method(function (){
console.log(this); // logs the "window" object
});
- 作为方法调用- 如果对象的某个属性具有某个函数的值,我们称之为方法。当该方法被调用时,该方法的值
this
就是该对象。
const details = {
name : "Marko",
getName(){
return this.name;
}
}
details.getName(); // returns Marko
// the "this" value inside "getName" method will be the "details" object
- 作为构造函数调用- 如果函数调用
new
时前面带有关键字,则该函数被称为function constructor
。将创建一个空对象并this
指向该对象。
function Employee(name, position, yearHired) {
// creates an empty object {}
// then assigns the empty object to the "this" keyword
// this = {};
this.name = name;
this.position = position;
this.yearHired = yearHired;
// inherits from Employee.prototype
// returns the "this" value implicitly if no
// explicit return statement is specified
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
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]); //the "this" object inside the "reduceAdd" function will be "obj1"
reduceAdd.call(obj2, 1, 2, 3, 4, 5); //the "this" object inside the "reduceAdd" function will be "obj2"
67.什么是记忆化(memoization)以及它有什么用处?
↑ 记忆化是指构建一个能够记住先前计算结果或值的函数的过程。
创建记忆化函数的目的是,如果该函数在上次计算中已经使用相同的参数执行过,则可以避免再次计算该函数。这可以节省时间,但缺点是,保存先前的结果会消耗更多内存。
68. 实现记忆辅助函数。
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");
此memoize辅助函数仅适用于接受一个参数的函数。我们需要创建一个接受多个参数的memoize辅助函数。
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);
69. 为什么要typeof null
返回object
?如何检查一个值是否为null
?
↑ typeof null == 'object'
始终会返回,因为这是JavaScript诞生以来true
的实现。有人提出将其更改为 ,但被拒绝了,因为这会导致现有项目和软件出现更多错误。null
typeof null == 'object'
typeof null == 'null'
我们可以使用===
或严格相等运算符来检查某个值是否为null
。
function isNull(value){
return value === null;
}
70.new
关键字起什么作用?
↑该关键字与构造new
函数一起使用来在JavaScript 中创建对象。
假设我们有下面的示例代码。
function Employee(name, position, yearHired) {
this.name = name;
this.position = position;
this.yearHired = yearHired;
};
const emp = new Employee("Marko Polo", "Software Developer", 2017);
该new
关键字有 4 个作用。
- 创建一个空对象。
- 将该空对象分配给该
this
值。 - 该函数将从functionName.prototype继承。
this
如果未return
使用显式语句,则返回。
在上图中,它首先会创建一个空对象{}
,然后
将this
值赋给该空对象this = {}
,并为其添加属性this
。由于我们没有显式return
声明,它会自动返回this
。