JavaScript是前端开发中很是重要的一门语言,浏览器是他主要运行的地方。JavaScript是一个很是有意思的语言,可是他有不少一些概念,你们常常都会忽略。好比说,原型,闭包,原型链,事件循环等等这些概念,不少JS开发人员都研究很少。javascript
因此今天,就来和你们看看下面几个问题,你们能够先思考一下,尝试做答。前端
前面的问题咱们都举例出来了,接下来咱们会从头至尾,一个个来分析咱们这些问题的答案,给你们一些学习的思路java
var a = 10; // 全局做用域,全局变量。a=10
function foo() {
// var a
//的声明将被提高到到函数的顶部。
// 好比:var a
console.log(a); // 打印 undefined
// 实际初始化值20只发生在这里
var a = 20; // local scope
}复制代码
图解在下面,好理解一点
因此问题1的答案是:undefined面试
var a = 10; // 全局使用域
function foo() { // TDZ 开始
// 建立了未初始化的'a'
console.log(a); // ReferenceError
// TDZ结束,'a'仅在此处初始化,值为20
let a = 20;
}复制代码
图解:
问题2答案:ReferenceError: a is not definedtypescript
这个问题,是循环结构会给你们带来一种块级做用域的误区,在for的循环的头部使用var声明的变量,就是单个声明的变量绑定(单个存储空间)。在循环过程当中,这个var声明的i变量是会随循环变化的。可是在循环中执行的数组push方法,最后其实是push了i最终循环结束的3这个值。因此最后push进去的全都是3。数组
// 误解做用域:认为存在块级做用域
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声明一个具备块级做用域的变量,这样为每一个循环迭代建立一个新的绑定。浏览器
// 使用ES6块级做用域
var array = [];
for (let i = 0; i < 3; i++) {
// 这一次,每一个'i'指的是一个新的的绑定,并保留当前的值。
// 所以,每一个箭头函数返回一个不一样的值。
array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]复制代码
还有解决这个问题的另一种解决方案就是使用闭包就行了。闭包
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] 复制代码
问题3答案:3,3,3并发
好了,如今有了前面这些知识,咱们能够看一下这道题的讲解过程:
实现步骤:异步
问题4答案:堆栈不会溢出。
他们有什么区别呢?
主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个低堆入堆栈,可是微任务队列老是在执行后返回到事件以前清空。因此,若是你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会从新渲染页面。
而后咱们再回到咱们前面讲的问题5中:
function foo() {
return Promise.resolve().then(foo);
}; 复制代码
咱们这段代码,每次咱们去调用【foo】的时候,都会在微任务队列上加另外一个【foo】的回调,所以事件循环没办法继续去处理其余的事件了(好比说滚动,点击事件等等),直到该队列彻底清空位置。所以,不会执行渲染,会被阻止。
问题5答案:不会响应。
var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {
// iterator 是一个具备 next 方法的对象,
// 它的返回至少有一个对象
// 两个属性:value&done。
// 返回一个 iterator 对象
return {
next: function() {
if (this._countDown === 3) {
const lastValue = this._countDown;
return { value: this._countDown, done: true };
}
this._countDown = this._countDown + 1;
return { value: this._countDown, done: false };
},
_countDown: 0
};
};
[...obj]; // 打印 [1, 2, 3]复制代码
问题6答案:如上是一种方案,能够避免TypeError异常。
var obj = { a: 1, b: 2 }; //a,b 都是可枚举属性
// 将{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-in遍历这个对象集合,那就是只能遍历出可枚举属性
for (let prop in obj) {
console.log(prop);
}
// 也就是只能打印
// a
// b
// c复制代码
图解
问题7答案:a、b、c
var x = 10; // 全局变量
var foo = {
x: 90,//foo对象的内部属性
getX: function() {
return this.x;
}
};
foo.getX(); // 此时是指向的foo对象,
//因此打印的是X属性 值就是90
let xGetter = foo.getX;//xGetter是在全局做用域,
//这里的this就是指向window对象
xGetter(); // 打印 10复制代码
问题8答案:10
ok,咱们的8道问题都解决了,若是你前面写的答案所有都正确,那么你很是棒!去面试前端工做起码12k起步了。就算作不出来或者作错了也没有关系,咱们都是不断经过犯错来学习的,一步步的理解错误,理解背后的缘由,才能进步。
更多技术好文,前端开发学习教程,欢迎关注公众号【前端研究所】看更多前端技术文章!