前端笔试题(基础20题)

第一题:浏览器控制台上会打印什么?

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章