Home  >  Article  >  Web Front-end  >  8 questions to test your JavaScript basics

8 questions to test your JavaScript basics

hzc
hzcforward
2020-06-20 09:53:512413browse
JavaScript is a fun language that we all love because of its nature. The browser is where JavaScript primarily runs, and the two work together in our services. JS has some concepts that people tend to take lightly and may sometimes ignore. Concepts like prototypes, closures, and event loops are still one of those obscure areas that most JS developers take a detour from. As we know, ignorance is a dangerous thing and can lead to mistakes.

If you want to read more high-quality articles, please click on the GitHub blog. Hundreds of high-quality articles are waiting for you every year!

Next, let’s take a look at a few questions. You can also try to think about them and then answer them.

Question 1: What will be printed on the browser console?

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

Question 2: If we use let or const instead of var, is the output the same

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

Question 3: What is in "newArray" element?

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

Question 4: If we run the 'foo' function in the browser console, will it cause a stack overflow error?

function foo() {
  setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};

Question 5: If you run the following function in the console, is the UI of the page (tab) still responsive?

function foo() {
  return Promise.resolve().then(foo);
};

Question 6: Can we somehow use the spread operation for the statement below without causing a type error

var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError

Question 7: When running the following code snippet, what will be printed on the console?

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);
}

Question 8: What value will xGetter() print?

var x = 10;
var foo = {
  x: 90,
  getX: function() {
    return this.x;
  }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

Answers

Now, let’s answer each question from beginning to end. I'll give you a brief explanation while trying to demystify these behaviors and provide some references.

Question 1: undefined

Analysis:

Variables declared using the var keyword will be promoted in JavaScript, and assigns the value undefined in memory. But initialization happens exactly where you assign a value to the variable. In addition, the variables declared by var are function-scoped, while let and const are block-scoped. So, this is what the process looks like:

var a = 10; // 全局使用域
function foo() {
// var a 的声明将被提升到到函数的顶部。
// 比如:var a

console.log(a); // 打印 undefined

// 实际初始化值20只发生在这里
   var a = 20; // local scope
}

Question 2: ReferenceError: a undefined.

Parsing:

let and const declarations allow a variable to have its scope restricted to the block, statement or expression in which it is used. Mode. Unlike var, these variables are not promoted and have a so-called temporary dead zone (TDZ). Attempting to access these variables in TDZ will raise ReferenceError because they can only be accessed when execution reaches declaration.

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

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

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

Question 3: [3, 3, 3]

Analysis:

in the for loop Declaring a variable with the var keyword in the header creates a single binding (storage space) for that variable. Read more about closures. Let's look at the for loop again.

// 误解作用域:认为存在块级作用域
var array = [];
for (var i = 0; i < 3; i++) {
  // 三个箭头函数体中的每个`&#39;i&#39;`都指向相同的绑定,
  // 这就是为什么它们在循环结束时返回相同的值&#39;3&#39;。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

If you declare a variable with block-level scope using let, a new binding is created for each loop iteration.

// 使用ES6块级作用域
var array = [];
for (let i = 0; i < 3; i++) {
  // 这一次,每个&#39;i&#39;指的是一个新的的绑定,并保留当前的值。
 // 因此,每个箭头函数返回一个不同的值。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

Another way to solve this problem is to use closures.

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]

Question 4: No overflow

Analysis:

The JavaScript concurrency model is based on the "event loop". When we say "the browser is the home of JS" what I really mean is that the browser provides the runtime environment to execute our JS code.

The main components of the browser include call stack, event loop, task queue and Web API. Global functions like setTimeout, setInterval, and Promise are not part of JavaScript, but part of the Web API.

The JS call stack is last-in-first-out (LIFO). The engine takes one function off the stack at a time and runs the code sequentially from top to bottom. Whenever it encounters some asynchronous code like setTimeout, it hands it over to Web API (arrow 1). Therefore, whenever an event is triggered, callback is sent to the task queue (arrow 2).

Event loopContinuously monitor the Task Queue and process callbacks one at a time in the order in which they are queued. Whenever the call stack is empty, the Event loopgets the callback and puts it into the stack(arrow 3) for processing . Remember, if the call stack is not empty, the event loop will not push any callbacks onto the stack .

Now, armed with this knowledge, let’s answer the previously mentioned question:

步骤

  1. 调用 foo()会将foo函数放入调用堆栈(call stack)
  2. 在处理内部代码时,JS引擎遇到setTimeout
  3. 然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空
  4. 计时器被设置为0,因此foo将被发送到任务队列(箭头2)。
  5. 由于调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。
  6. 进程再次重复,堆栈不会溢出。

问题5 : 不会响应

解析:

大多数时候,开发人员假设在事件循环图中只有一个任务队列。但事实并非如此,我们可以有多个任务队列。由浏览器选择其中的一个队列并在该队列中处理回调

在底层来看,JavaScript中有宏任务和微任务。setTimeout回调是宏任务,而Promise回调是微任务

主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面、

现在,当你在控制台中运行以下代码段

function foo() {
  return Promise.resolve().then(foo);
};

每次调用'foo'都会继续在微任务队列上添加另一个'foo'回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。 因此,它会阻止渲染。


问题6 : 会导致TypeError错误

解析:

展开语法 和 for-of 语句遍历iterable对象定义要遍历的数据。ArrayMap 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用iterable和iterator协议使它们可迭代。

Mozilla文档中,如果一个对象实现了@@iterator方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象)必须有一个带有@@iterator键的属性,这个键可以通过常量Symbol.iterator获得。

上述语句可能看起来有点冗长,但是下面的示例将更有意义:

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]

还可以使用 generator 函数来定制对象的迭代行为:

var obj = {x:1, y:2, z: 3}
obj[Symbol.iterator] = function*() {
  yield 1;
  yield 2;
  yield 3;
}
[...obj]; // 打印 [1, 2, 3]

问题7 : a, b, c

解析:

for-in循环遍历对象本身的可枚举属性以及对象从其原型继承的属性。 可枚举属性是可以在for-in循环期间包含和访问的属性。

var obj = { a: 1, b: 2 };
var descriptor = Object.getOwnPropertyDescriptor(obj, "a");
console.log(descriptor.enumerable); // true
console.log(descriptor);
// { value: 1, writable: true, enumerable: true, configurable: true }

现在你已经掌握了这些知识,应该很容易理解为什么我们的代码要打印这些特定的属性

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

问题8 : 10

解析:

在全局范围内初始化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.bindthis的值绑定到foo对象来创建新函数。

let getFooX = foo.getX.bind(foo);
getFooX(); // 90

就这样! 如果你的所有答案都正确,那么干漂亮。 咱们都是通过犯错来学习的。 这一切都是为了了解背后的“原因”。


推荐教程:《JS教程

The above is the detailed content of 8 questions to test your JavaScript basics. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete