考题列表
- 为什么此代码obj.someprop.x会引发错误?
- 什么是event.target?
- 什么是event.currentTarget?
- == 和 === 有什么区别?
- 为什么在 JS 中比较两个相似的对象时返回 false?
- !! 运算符能做什么?
- 如何在一行中计算多个表达式的值?
- 什么是提升?
- 什么是作用域?
- 什么是闭包?
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,因为两者的类型和值相同。
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 的声明。