Author: Chidume Nnamdi
Translator: Frontend Xiaozhi
Source: smashingmagazineLike and look again, develop a habit
This article
GitHub
has been included on https://github.com/qq449245884/xiaozhi . There are more categories of highly praised articles in the past, and a lot of my documents and tutorial materials have been sorted out. Welcome to Star and perfect. You can refer to the test center for review during the interview. I hope we can have something together.
JavaScript is an interesting language, and we all like it because of its nature. The browser is the main place where JavaScript runs, and the two work together in our service. JS has some concepts, people tend to take it lightly, and sometimes they may ignore it. Concepts such as prototypes, closures, and event loops are still one of the obscure areas where most JS developers take a detour. As we know, ignorance is a dangerous thing, and it can lead to errors.
you want to read more high-quality articles, please [Poke GitHub Blog][1]\, hundreds of high-quality articles a year are waiting for you!
Next, let's take a look at a few questions, you can also try to think about it, and then answer.
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, will the output be the same
var a = 10;
function foo() {
console.log(a); // ??
let a = 20;
}
foo();
Question 3: What are the elements in "newArray"?
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, does the UI of the page (tab) still respond?
function foo() {
return Promise.resolve().then(foo);
};
Question 6: Can we use the expansion operation for the following statement in some way without causing a type error
var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError
Question 7: When I run 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 ??
answer
Now, let us answer each question from beginning to end. I will give you a brief explanation, try to uncover the mystery of these behaviors, and provide some reference materials.
Question 1: undefined
Analysis:
Variables declared using the var
keyword will be promoted in JavaScript, and the value undefined
will be allocated in memory. But initialization happens exactly where you assign a value to the variable. In addition, var
are [function-scoped][2], while let
and const
are block-scoped. So, this is how this 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
.
Analysis:
let
and const
declarations allow a variable to be limited in its scope by the block, statement, or expression it uses. Unlike var
, these variables are not promoted, and there is a so-called Temporary Dead Zone (TDZ) . Attempting to access these variables in TDZ ReferenceError
because they can only be accessed when the execution reaches the declaration.
var a = 10; // 全局使用域
function foo() { // TDZ 开始
// 创建了未初始化的'a'
console.log(a); // ReferenceError
// TDZ结束,'a'仅在此处初始化,值为20
let a = 20;
}
The following table summarizes the promotion behavior and usage domains corresponding to variables declared by different keywords used in JavaScript:
- -
Question 3: [3, 3, 3]
Analysis:
In for
head statement cycle with var
variable keyword that will create a single variable bindings (storage space). Read more about [closure][3]. Let's look at the for loop again.
// 误解作用域:认为存在块级作用域
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]
If you use let
declare a variable with block-level scope, a new binding is created for each loop iteration.
// 使用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]
Another way to solve this problem is to use [closure][4].
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: Will not overflow
Analysis:
The JavaScript concurrency model is based on the "event loop". When we say "the browser is the home of JS" I really mean that the browser provides a 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 Web API. The visual form of the JavaScript environment is as follows:
The JS call stack is last in first out (LIFO). The engine removes a function from the stack at a time, and then runs the code sequentially from top to bottom. Whenever it encounters some asynchronous code, such as setTimeout
, it Web API
it to 06078da915e6f0 (arrow 1). Therefore, each time an event is triggered, callback
will be sent to the task queue (arrow 2).
Event loop constantly monitors the Task Queue and processes one callback at a time in the order in which they are queued. Whenever the call stack is empty, the Event loop gets the callback and puts it into the stack (stack) (arrow 3) for processing. Remember, if the call stack is not empty, will not push any callbacks onto the stack .
Now, with this knowledge, let us answer the questions mentioned earlier:
step
- Calling
foo()
will put thefoo
function into the call stack . - When dealing with internal code, the JS engine encountered
setTimeout
. - Then pass the
foo
callback function to WebAPIs (arrow 1) and return from the function, the call stack is empty again - The timer is set to 0, so
foo
will be sent to
task queue
(Arrow 2).
- Since the call stack is empty, the event loop will select the
foo
callback and push it into the call stack for processing. - The process repeats again and the stack will not overflow.
The operation diagram is as follows:
\![Picture description][5]
- -
Question 5: Will not respond
Analysis:
Most of the time, developers assume that
is only one task queue in the 16078da915e893 event loop But this is not the case, we can have multiple task queues. Wherein a queue is selected by the browser and the queue handling callbacks
。
At the bottom level, there are macro tasks and micro tasks in JavaScript. setTimeout
callback is the macrotask , and the Promise
callback is the .
The main difference lies in the way they are implemented. Macro tasks are pushed onto the stack one at a time in a single cycle, but the micro task queue is always emptied after execution before returning to the event loop. Therefore, if you add items to this queue at the speed of processing items, you will always be processing microtasks. Only when the microtask queue is empty, the event loop will re-render the page,
Now, when you run the following code snippet in the console
function foo() {
return Promise.resolve().then(foo);
};
Each call to ' foo
' will continue to add another ' foo
' callback to the microtask queue, so the event loop cannot continue to process other events (scrolling, clicking, etc.) until the queue is completely emptied. Therefore, it will prevent rendering.
- -
Question 6: Will cause TypeError
Analysis:
[Expand Syntax][6] and [for-of][7] The statement traverses the iterable
object to define the data to be traversed. Array
or Map
are built-in iterators with default iteration behavior. Objects are not iterable, but they can be made iterable by using the [iterable][8] and [iterator][9] protocols.
In the Mozilla document, if an object implements the @@iterator
method, then it is iterable, which means that the object (or an object on its prototype chain) must have a property with the key @@iterator
Symbol.iterator
by the constant 06078da915e9ba.
The above statement may seem a bit verbose, but the following example will make more sense:
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]
You can also use the [generator][10] function to customize the iteration behavior of the object:
var obj = {x:1, y:2, z: 3}
obj[Symbol.iterator] = function*() {
yield 1;
yield 2;
yield 3;
}
[...obj]; // 打印 [1, 2, 3]
- -
Question 7: a, b, c
Analysis:
for-in
loops through the [enumerable properties][11] of the object itself and the properties inherited by the object from its prototype. Enumerable attributes are attributes that can be included and accessed during the cycle of 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 }
Now that you have mastered this knowledge, it should be easy to understand why our code prints these specific attributes
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
- -
\#\#\#\# Question 8: 10
Analysis:
x
in the global scope, it becomes a property of the window object (not in strict mode). Take a look at the following code:
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
We can assert:
window.x === 10; // true
this
always points to the object calling the method. Therefore, in foo.getx()
, it points to the foo
object and returns the value of 90
In xGetter()
case, the this
point window object, returns window in x
value, i.e. 10
.
To get the value of foo.x
, you can create a new function Function.prototype.bind
to this
the value of foo
let getFooX = foo.getX.bind(foo);
getFooX(); // 90
that's all! If all of your answers are correct, then do pretty. We all learn by making mistakes. All this is to understand the "reason" behind it.
- -
may have bugs that may exist after code deployment cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, I would like to recommend a useful BUG monitoring tool Fundebug
Original: https://dev.to/aman_singh/so-you-think-you-know-javascript-5c26
communicate with
The dry goods series articles are summarized as follows. I think it’s good to order a Star. Welcome to join the group to learn from each other.
https://github.com/qq449245884/xiaozhi
I am Xiaozhi, the author of the I am a fan of learning front-end technology. I will often share what I have learned and read , and encourage each other on the way to advancement!
attention to the public account, and reply to 16078da915ec67 welfare background, you can see the welfare, you know.
[1]: https://www.fundebug.com/?utm\_source=xiaozhi
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。