8个问题考查你是否真的懂js

前面的话

小柒整理的这8个问题,考查范围很广,js常见知识点都考查到了。比如变量提升、作用域、闭包、js运行机制、宏任务(微任务)、this指向以及es6中的let、Symbol、迭代器、生成器。

题目

问题1:浏览器控制台上会打印什么?
var a = 10;
function foo() {
    console.log(a); // ??
    var a = 20;
}
foo();

解析:
本题考查变量提升。使用var关键字声明的变量会被提升,代码相当于这样:

var a = 10;
function foo() {
	 var a ;
    console.log(a); // ??
     a = 20;
}
foo();

则输出undefined

问题2:如果我们使用 let 或 const 代替 var,输出是否相同
var a = 10;
function foo() {
    console.log(a); // ??
    let a = 20;
}
foo();   

let与const不存在变量提升,并且有一个暂时死区(TDZ), 试图访问TDZ中的变量将会引发错误,只有在执行声明语句之后才能访问。

var a = 10; // 全局使用域
function foo() { 
// 死区 TDZ 开始

// 创建了未初始化的'a'
    console.log(a); // ReferenceError

// 死区TDZ结束,'a'仅在此处初始化,值为20
    let a = 20;
}

注意:不要觉得let不能变量提升,就可以访问全局作用域中的变量a。 只要访问上面的TDZ中的变量就会发生错误。

问题3:"newArray"中有哪些元素?
var array = [];
for(var i = 0; i <3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??   

解析:

本题考查闭包。在for循环中每一次push一个方法,for循环完成之后,push了三个方法。此时i已经是3。在使用map方法对每一项进行调用,并且返回,而i已经是3。所以返回[3,3,3]

箭头函数不好看,可以将上面的代码写成:

var array = [];
for(var i = 0; i <3; i++) {
 array.push(function() {return i});
}
var newArray = array.map(function(item){
    return item();
});
console.log(newArray); // ??   

如果想输出[0,1,2]该如何改?

  • 方法1:将var改为let

    var array = [];
    for(let i = 0; i <3; i++) {
     array.push(() => i);
    }
    var newArray = array.map(el => el());
    console.log(newArray); // ??   
    
  • 方法2:改成闭包

    var array = [];
    	for(let i = 0; i <3; i++) {
    	 array[i] = (function(i) {
    	 	return function() {
    	 		return i;
    	 	}
    	 }(i))
    	}
    	var newArray = array.map(el => el());
    	console.log(newArray); // ??   
    
问题4:如果我们在浏览器控制台中运行’foo’函数,是否会导致堆栈溢出错误?
function foo() {
  setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};  
foo();  

解析:

本题考查js运行过程。

一张图解释:
在这里插入图片描述

  • 调用foo()会将foo函数放入调用堆栈(call stack)
  • 在处理内部代码时,js引擎遇到setTimeout,将其回调函数foo传递给WebAPI,此时调用堆栈为空
  • 计时器被设置为0,因此回调函数foo将被发送到任务队列
  • 由于调用堆栈是空,所以事件循环将选择foo回调并将其推入堆栈进行处理。(一定等调用堆栈为空,才能调用)
  • 进程再次重复上述过程,堆栈不会溢出。

示意图:
在这里插入图片描述

问题5: 如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应
function foo() {
  return Promise.resolve().then(foo);
};  

解析:

本题考查宏任务、微任务

在上题中,我们碰到的setTimeout就是一个宏任务,宏任务将在一个循环周期后一次一个被推入调用堆栈被执行。但是本题的promise的then方法是一个微任务,它是在同步任务之后执行的始终,都在单个循环周期里面。

本题中永远都在处理微任务,单个事件循环永远不会结束,永远不会发生响应。也会阻塞其他的事件渲染。

问题6: 我们能否以某种方式为下面的语句使用展开运算而不导致类型错误
var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError 

解析:

本题考查es6语法

想要被...for-of遍历,就必须有内置的迭代器或者自定义一个迭代器。Array、Map或Set都是具有默认迭代行为的内置迭代器。但是对象是不可迭代的,上面的代码,会报错。

如果想让对象能够使用...扩展运算符,就必须自定义一个迭代器。就要用到ES6中的Symbol.iterator。对象拥有这个键,就可以指定一个迭代器方法。

方法1: 直接构造迭代器方法,让其返回一个iterator对象

var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {
	// iterator是一个具有next方法的对象,它的返回至少有一个对象,两个属性:value & done
	return {
        next: function() {
			if (this.count === 3) {
				// 设置done为true,表示不能再遍历
                return { value: this.count, done: true };
			}
            this.count = this.count + 1;
			return {value: this.count, done: false};
		},
		count: 0
	};
};
console.log([...obj]); // [1, 2, 3]

方法二:使用Generator生成器,它是一种返回迭代器的函数

var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function*() {
	yield 1;
    yield 2;
    yield 3;
}
console.log([...obj]); // [1, 2, 3]
}
问题7:运行以下代码片段时,控制台上会打印什么?
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);
}  

解析:

本题考查:j对象上的访问属性以及fot-in循环

  • 首先为obj设置原型为{c:3}
  • 在obj上添加一个属性d,但是其enumerable:为false,表示d属性不能被遍历
  • 最后for-in循环可以遍历对象本身的可枚举属性以及对象从其原型上继承的属性。
  • 所以打印a、b、c
问题8:xGetter() 会打印什么值?
var x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

解析:

本题考查this指向。

  • foo.getX() this指向foo,打印90
  • 将foo.getX保存出来赋值给xGetter,this指向window。打印10

参考文章:

发布了238 篇原创文章 · 获赞 175 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41257129/article/details/104155269
今日推荐