第一题:浏览器控制台上会打印什么?
var a = 10;
function foo() {
console.log(a); // ??
var a = 20;
}
foo();
如果我们使用 let 或 const 代替 var,输出是否相同?
var a = 10;
function foo() {
console.log(a); // ??
let a = 20;
}
foo();
答案解析:
let和const声明可以让变量在其作用域上受限于它所使用的块、语句或表达式。与var不同的是,这些变量没有被提升,并且有一个所谓的暂时死区(TDZ)。试图访问TDZ中的这些变量将引发ReferenceError,因为只有在执行到达声明时才能访问它们。最终答案是:
undefined
ReferenceError
第二题:浏览器控制台上会打印什么?
var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, {c: 3});
Object.defineProperty(obj, 'd', { value: 4, enumerable: false });
// what properties will be printed when we run the for-in loop?
for(let prop in obj) {
console.log(prop);
}
答案解析:
for-in循环遍历对象本身的可枚举属性以及对象从其原型继承的属性。可枚举属性是可以在for-in循环期间包含和访问的属性。
var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 属性
// 将{c:3}设置为'obj'的原型,并且我们知道
// for-in 循环也迭代 obj 继承的属性
// 从它的原型,'c'也可以被访问。
Object.setPrototypeOf(obj, { c: 3 });
// 我们在'obj'中定义了另外一个属性'd',但是
// 将'enumerable'设置为false。 这意味着'd'将被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });
for (let prop in obj) {
console.log(prop);
}
// 打印
// a
// b
// c
第三题:看下面的代码,并说出"newArray"中有哪些元素?
var array = [];
for(var i = 0; i <3; i++) {
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??
答案解析:
在for循环的头部声明带有var关键字的变量会为该变量创建单个绑定(存储空间),接下来在{}中分别创建了闭包,但是这些闭包中三个箭头函数体中的每个'i'
都指向相同的绑定,让我们通过代码再看一次for循环。
// 误解作用域:认为存在块级作用域
var array = [];
for (var i = 0; i < 3; i++) {
// 三个箭头函数体中的每个`'i'`都指向相同的绑定,
// 这就是为什么它们在循环结束时返回相同的值'3'。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]
如果使用 let 声明一个具有块级作用域的变量,则为每个循环迭代创建一个新的绑定。
var array = [];
for (let i = 0; i < 3; i++) {
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
使用ES6块级作用域,这一次,每个'i'指的是一个新的的绑定,并保留当前的值,因此,每个箭头函数返回一个不同的值。
解决这个问题的另一种方法是使用闭包。
let array = [];
for (var i = 0; i < 3; i++) {
array[i] = (function(x) {
return function() {
return x;
};
})(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]
第四题:如果我们在浏览器控制台中运行'foo'函数,是否会导致堆栈溢出错误?
function foo() {
setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};
答案解析:
JavaScript并发模型基于“事件循环”。
浏览器的主要组件包括调用堆栈,事件循环,任务队列和Web API。像setTimeout,setInterval和Promise这样的全局函数不是JavaScript的一部分,而是 Web API 的一部分。
JS调用栈是后进先出(LIFO)的。引擎每次从堆栈中取出一个函数,然后从上到下依次运行代码。每当它遇到一些异步代码,如setTimeout,它就把它交给Web API。因此,每当事件被触发时,callback 都会被发送到任务队列。
事件循环(Event loop)不断地监视任务队列(Task Queue),并按它们排队的顺序一次处理一个回调。每当调用堆栈(call stack)为空时,Event loop获取回调并将其放入堆栈(stack )中进行处理。请记住
,如果调用堆栈不是空的,则事件循环不会将任何回调推入堆栈。
问题的代码执行步骤:
- 调用 foo()会将foo函数放入调用堆栈(call stack)。
- 在处理内部代码时,JS引擎遇到setTimeout。
- 然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空
- 计时器被设置为0,因此foo将被发送到任务队列<Task Queue>(箭头2)。
- 由于调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。
- 进程再次重复,堆栈不会溢出。
第五题:如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应?
function foo() {
return Promise.resolve().then(foo);
}
答案解析:
大多数时候,开发人员假设在事件循环<event loop>中只有一个任务队列。但事实并非如此,我们可以有多个任务队列。由浏览器选择其中的一个队列并在该队列中处理回调<callbacks>。
在底层来看,JavaScript中有宏任务和微任务。setTimeout回调是宏任务,而Promise回调是微任务。
主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面、
现在,当你在控制台中运行问题的代码片段时,
每次调用'foo'都会继续在微任务队列上添加另一个'foo'回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。因此,它会阻止渲染。
第六题:下面的语句使用展开运算会导致类型错误吗?
var obj = { x: 1, y: 2, z: 3 };
[...obj]
答案解析:
展开语法 和 for-of 语句遍历iterable对象定义要遍历的数据。Array 或Map 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用iterable和iterator协议使它们可迭代,所以题目中的代码会导致TypeError错误。
第七题:xGetter() 会打印什么值?
var x = 10;
var foo = {
x: 90,
getX: function() {
return this.x;
}
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??
答案解析:
在全局范围内初始化x时,它成为window对象的属性(不是严格的模式)。看看下面的代码:
var x = 10; // global scope
var foo = {
x: 90,
getX: function() {
return this.x;
}
};
foo.getX(); // prints 90
let xGetter = foo.getX;
xGetter(); // prints 10
咱们可以断言:
window.x === 10; // true
this 始终指向调用方法的对象。因此,在foo.getx()的例子中,它指向foo对象,返回90的值。而在xGetter()的情况下,this指向 window对象, 返回 window 中的x的值,即10。
要获取 foo.x的值,可以通过使用Function.prototype.bind将this的值绑定到foo对象来创建新函数。
let getFooX = foo.getX.bind(foo);
getFooX(); // 90
第八题:读代码,并写出打印结果。
var a = 1
function foo(){
if(!a){
var a = 2
}
alert(a)
}
console.log(foo())
答案解析:
涉及到的知识点有作用域,变量提升。
因为var是函数级作用域,foo函数中出现var a=2 的存在,
就默认在函数内顶端 声明var a;此时这个a没有被赋值所以是undefined;
然后执行if(!a)等价于!undefined肯定是true。然后给a赋值为2.
所以打印的是2。
第九题:读代码,并写出打印结果。
function test(person) {
person.age = 26
person = {
name: 'hzj',
age: '18'
}
return person
}
const p1 = {
name: 'fyq',
age: 19
}
const p2 = test(p1)
console.log(p1)
console.log(p2)
答案解析:
在函数传参的时候传递的是对象在堆中的内存地址值,test函数中的实参person是p1对象的内存地址,通过调用person.age = 26确实改变了p1的值,但随后person变成了另一块内存空间的地址,并且在最后将这另外一份内存空间的地址返回,赋给了p2。所以打印结果是
{name: "fyq", age: 26}
index.html:46 {name: "hzj", age: "18"}
第十题:读代码,并写出打印结果。
const box = {
x: 10,
y: 20
};
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape)
答案解析:
Object.freeze使得无法添加、删除或修改对象的属性(除非属性的值是另一个对象)。
当我们创建变量 shape并将其设置为等于冻结对象 box时, shape指向的也是冻结对象。你可以使用 Object.isFrozen检查一个对象是否被冻结,上述情况, Object.isFrozen(shape)将返回 true。
由于 shape被冻结,并且 x的值不是对象,所以我们不能修改属性 x。x仍然等于 10, {x:10,y:20}被打印。
注意,题目中的代码对属性 x进行修改,在严格模式下,会导致抛出TypeError异常。
index.html:24 Uncaught TypeError: Cannot assign to read only property 'x' of object '#<Object>'
第十一题:读代码,并写出打印结果。
var number = 50
var obj = {
number: 60,
getNum: function () {
var number = 70
return this.number
}
}
console.log(obj.getNum())
console.log(obj.getNum.call(undefined))
console.log(obj.getNum.call({ number: 20 }))
答案解析:
第一个结果:60
第二个结果:50
什么参数都不传,或者第一个参数传null或者undefined的时候,执行上下文都会指向window
第三个结果:20
第十二题:读代码,并写出打印结果。
function checkAge(age) {
if (age < 18) {
const message = 'too young'
} else {
const message = 'too simple'
}
return message
}
console.log(checkAge(21))
答案解析:
const和 let声明的变量是具有块级作用域的,块是大括号( {})之间的任何东西, 即上述情况 if/else语句的花括号。由于块级作用域,我们无法在声明的块之外引用变量,因此抛出 Uncaught ReferenceError: message is not defined
。
第十三题:读代码,并写出打印结果。
const { cname: myName } = { cname: "Lydia" };
console.log(cname);
答案解析:
当我们从右侧的对象解构属性 cname时,我们将其值 Lydia分配给名为 myName的变量。
使用 {cname:myName},我们是在告诉JavaScript我们要创建一个名为 myName的新变量,并且其值是右侧对象的 cname属性的值。
当我们尝试打印 cname,一个未定义的变量时,就会引发 index.html:18 Uncaught ReferenceError: cname is not defined
。
第十四题:读代码,并写出打印结果。
let newList = [1,2,3].push(4)
console.log(newList.push(5))
答案解析:
push方法返回数组的长度,而不是数组本身!通过将 newList设置为 [1,2,3].push(4),实际上 newList等于数组的新长度:4。
然后,尝试在 newList上使用 .push方法。由于 newList是数值 4,抛出index.html:18 Uncaught TypeError: newList.push is not a function
。
第十五题:读代码,并写出打印结果。
function giveLady() {
return 'HHHHHHH'
}
const giveGentMan = () => 'MMMMMMMMMMMMM'
console.log(giveLady.prototype)
console.log(giveGentMan.prototype)
答案解析:
常规函数,例如 giveLydiaPizza函数,有一个 prototype属性,它是一个带有 constructor属性的对象(原型对象)。然而,箭头函数,例如 giveLydiaChocolate函数,没有这个 prototype属性。尝试使用 giveLydiaChocolate.prototype访问 prototype属性时会返回 undefined。
第十六题:读代码,并写出打印结果。
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
答案解析:
...args是剩余参数,剩余参数的值是一个包含所有剩余参数的数组,并且只能作为最后一个参数。上述示例中,剩余参数是第二个参数,这是不可能的,并会抛出语法错误。
function getItems(fruitList, favoriteFruit, ...args) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
上述例子是有效的,将会返回数组:['banana','apple','orange','pear']
第十七题:读代码,并写出打印结果。
function nums(a, b) {
if (a > b)
console.log("a is bigger")
else
console.log("b is bigger")
return
a + b
}
console.log(nums(4, 2))
console.log(nums(1, 2))
答案解析:
在JavaScript中,我们不必显式地编写分号( ;),但是JavaScript引擎仍然在语句之后自动添加分号。这称为自动分号插入。例如,一个语句可以是变量,或者像 throw、 return、 break这样的关键字。
在这里,我们在新的一行上写了一个 return语句和另一个值 a+b。然而,由于它是一个新行,引擎并不知道它实际上是我们想要返回的值。相反,它会在 return后面自动添加分号。你可以这样看:
return;
a + b
这意味着永远不会到达 a+b,因为函数在 return关键字之后停止运行。如果没有返回值,就像这里,函数返回 undefined。注意,在 if/else关键词之后不会自动插入分号
所以输出结果为
a is bigger
undefined
b is bigger
undefined
第十八题:读代码,并写出打印结果。
const info = {
[Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
答案解析:
Symbol类型是不可枚举的。Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组。记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。
Symbol可以用来表示表示完全唯一的值,在对象中使用Symbol作为属性,可以防止对象意外名称冲突。但是可以通过Object.getOwnPropertySymbols(info)来访问info的所有属性。[Symbol(a)]
第十九题:读代码,并写出打印结果。
const geetList = ([x, ...y]) => [x, y]
const getUser = user => ({ name: user.nameage: user.age }
const list = [1, 2, 3, 4]
const user = { name: 'Lydia', age: 21 }
console.log(geetList(list))
console.log(getUser(user))
答案解析:
Uncaught SyntaxError: Unexpected token :
原因:getUser函数接收一个对象。对于箭头函数,如果只返回一个值,我们不必编写花括号。但是,如果您想从一个箭头函数返回一个对象,您必须在圆括号之间编写它,否则不会返回任何值!
正确的写法应该是这样
const geetList = ([x, ...y]) => [x, y]
const getUser = user => ({ name: user.name, age: user.age })
const list = [1, 2, 3, 4]
const user = { name: 'Lydia', age: 21 }
console.log(geetList(list))
console.log(getUser(user))
输出内容
[1, Array(3)]
{name: "Lydia", age: 21}
第二十题:读代码,并写出打印结果。
var arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr = arr.map(function (x) {
return x * x
}).reduce(function (acc, x, i) {
acc.push(x)
acc.push(i)
return acc
}, []).filter(function (x) {
return x % 2
}).join(",")
console.log(arr)
答案解析:
var arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr = arr.map(function (x) {
return x * x
})
// [1, 4, 9, 16, 25, 36, 49, 64]
.reduce(function (acc, x, i) {
acc.push(x)
acc.push(i)
return acc
}, [])
// [1, 0, 4, 1, 9, 2, 16, 3, 25, 4, 36, 5, 49, 6, 64, 7]
.filter(function (x) {
return x % 2
})
// [1, 1, 9, 3, 25, 5, 49, 7]
.join(",")
console.log(arr)
// 1,1,9,3,25,5,49,7
acc不包括当前的x,如果把reduce的第二个参数改成0,然后第一个函数参数内的代码改为return acc+x,则返回值是数组的合。
0%2,结果是0,2%2,结果是0,-2%2,结果是-0,这三个转换为布尔值,结果都是false
网友评论