Javascript Ninja Cheats Reading Notes

Fundamental differences between JavaScript and other languages:

Functions are first-class citizens (first-class objects) - In JavaScript, functions coexist with other objects and can be used like any other object. Functions can be created literally, assigned to variables, passed as function parameters, and even returned from functions as return values.

Function Closures - This fundamentally exemplifies the importance of functions to JavaScript. We understand it shallowly. Closure is when a function actively maintains external variables used in the function, then the function is a closure.

Scope - Until recently, JavaScript didn't have (like C) variables at block scope, instead relying on function-level variables and global variables.

Prototype-based object-orientation - Unlike other mainstream object-oriented languages ​​that use class-based object-orientation, JavaScript uses prototype-based object-orientation.

The tight combination of objects, prototypes, functions, and closures make up JavaScript.

The lifecycle of a web application:

The cycle of the client web application starts from the user specifying a certain website address (or clicking a certain link), and consists of two steps: page construction and event processing
insert image description here
From the user's point of view, the browser constructs the 2), the server processes the request (sequence number 3) and forms a response that usually consists of HTML, CSS, and JavaScript code. When the browser receives the response (sequence number 4), our client application begins its life cycle. Since the client web application is a graphical user interface (GUI) application, its life cycle is similar to other GUI applications (such as standard desktop applications or mobile applications), and its execution steps are as follows:

1. Page construction - creating the user interface;

2. Event processing—enter the loop (serial number 5) to wait for the occurrence of the event (serial number 6), and call the event handler after it occurs.

The life cycle of the application ends when the user closes or leaves the page (No. 7).
insert image description here
The page building phase starts with the browser receiving the page code. Its execution is divided into two steps: HTML parsing and DOM construction, and the execution of JavaScript code.

HTML parsing and DOM construction:

The page construction phase begins when the browser receives the HTML code, which provides the basis for the browser to build the UI of the page. By parsing the received HTML code, build HTML elements one by one, and build DOM.
Although the DOM is created from HTML and the two are closely related, it should be emphasized that they are not the same. You can think of the HTML code as the blueprint from which the browser page UI builds the initial DOM. To build each DOM correctly, the browser also fixes problems it finds in blueprints.
insert image description here
insert image description here
During the page construction phase, the browser encounters a special type of HTML element—the script element, which is used to include JavaScript code. Whenever a script element is parsed, the browser stops building the DOM from HTML and starts executing JavaScript code.

Execute JavaScript code:

All JavaScript code contained within script elements is executed by the browser's JavaScript engine.

In general, JavaScript code can modify the DOM structure to any extent: it can create new nodes or remove existing DOM nodes. But it still can't do certain things, such as select and modify nodes that haven't been created yet. That's why the script element should be placed at the bottom of the page . This way, we don't have to worry about whether an HTML element has already been loaded into the DOM.

When the browser encounters a script node during the page construction phase, it will stop the construction of HTML to DOM and start executing JavaScript code instead, which is to execute the global JavaScript code contained in the script element.

Once the JavaScript engine executes to the last line of JavaScript code in the script element, the browser exits the JavaScript execution mode and continues to build the remaining HTML into DOM nodes. During this period, if the browser encounters a script element again, the construction from HTML to DOM is paused again, and the JavaScript runtime environment begins to execute the remaining JavaScript code. As long as there are unfinished HTML elements and unfinished JavaScript codes, the following two steps will always be executed alternately.

1. Build HTML into DOM.

2. Execute JavaScript code.

Finally, when the browser has processed all HTML elements, the page building phase is over. The browser then enters the second part of the application lifecycle: event handling.

Event handling:

The JavaScript code executed during the page construction phase will not only affect the global application state and modify the DOM, but also register event listeners (or processors). This type of listener will be called and executed by the browser when the event occurs.

The core idea of ​​the browser execution environment is based on: only one code fragment can be executed at a time, which is the so-called single-threaded execution model.

insert image description here
Event processing - At the same time, only one of many different events can be processed, and the processing order is the order in which the events are generated. The event processing phase relies heavily on the event queue, and all events are stored in the event queue in the order in which they appear. The event loop checks the head of the event queue, and if an event is detected, the corresponding event handler is called.

function

Anything an object can do, a function can do too. A function is also an object, the only special thing about it is that it is callable, which means that the function will be called in order to perform some action.

Callback

That is, during the execution process, the function we created will be "called back" by other functions at an appropriate point in time later

Add attributes to functions

The fun of functions as objects - we can add properties to functions.

var ninja = {
    
    }
ninja.name = "hitsuke";--- 创建新对象并为其分配一个新属性
var wieldSword = function(){
    
    };
wieldSword.swordType = "katana";--- 创建新函数并为其分配一个新属性

store a collection of unique functions

var store = {
    
    
 nextId: 1,--- 跟踪下一个要被复制的函数
 cache: {
    
    },--- 使用一个对象作为缓存,我们可以在其中存储函数  add: function(fn) {
    
     
   if (!fn.id) {
    
    
    fn.id = this.nextId++;
    this.cache[fn.id] = fn;
    return true;
  }
}--- 仅当函数唯一时,将该函数加入缓存 };
function ninja(){
    
    } assert(store.add(ninja), "Function was safely added.");
assert(!store.add(ninja),"But it was only added once.");--- 测试上面的代码按预期工作

After attaching a property to a function, we can refer to that property. This way the example ensures that the ninja function is only added once to the function.
The self-memory function
is as follows, the cached answers is an attribute of the isPrime function itself, as long as the function is still there, the cache also exists.

function isPrime(value) {
    
    
  if (!isPrime.answers) {
    
      
    isPrime.answers = {
    
    };--- 创建缓存
  }  
 if (isPrime.answers[value] !== undefined) {
    
    
  return isPrime.answers[value];
 }--- 检查缓存的值
 var prime = value !== 0 && value !== 1; // 1 is not a prime 
  for (var i = 2; i < value; i++) {
    
    
  if (value % i === 0) {
    
        
    prime = false;    
    break;
   }
 }
 return isPrime.answers[value] = prime;--- 存储计算的值 
}

assert(isPrime(5), "5 is prime!");
assert(isPrime.answers[5], "The answer was cached!");--- 测试该函数 是否正常工作

function expression

Functions that are always part of other expressions (either as rvalues ​​in assignment expressions, or as arguments to other functions) are called function expressions

Execute the function immediately

insert image description here
Immediately invokes 4 different versions of the function expression theme:
insert image description here

arrow function

insert image description here

Function Parameters and Actual Parameters

1. The formal parameters are the variables listed when we define the function.

Function parameters are specified when the function is defined.

2. The actual parameter is the value passed to the function when we call the function.

insert image description here

insert image description here

function call

The implicit parameters arguments and this.

These implicit parameters are not explicitly defined in the function declaration, but are passed to the function by default and can be accessed normally within the function.

arguments parameter

The main function of the arguments object is to allow us to access all the parameters passed to the function, even if some parameters are not associated with the formal parameters of the function.

The arguments object has a property named length that indicates the exact number of actual arguments.

The arguments object is just an array-like structure, but it is not a JavaScript array. If you try to use array methods on the arguments object, you will find that an error will eventually be reported.

In most cases the remaining parameters can be used instead of the arguments parameter. The remaining parameters are real Array instances, which means you can use all array methods directly on it.

this parameter: function context

The this parameter is an important part of object-oriented JavaScript programming, representing the object associated with the function call. Therefore, it is usually called function context.

Call a function in 4 ways

As a function (function) - skulk (), directly called.

As a method (method)—ninja.skulk(), it is associated with an object to realize object-oriented programming.

As a constructor (constructor), new Ninja(), instantiates a new object.

Through the apply or call method of the function - skulk.apply(ninja) or skulk.call(ninja).

1. Called directly as a function

function ninja() {
    
    };
ninja();--- 函数定义作为函数被调用
var samurai = function(){
    
    };
samurai();--- 函数表达式作为函数被调用
(function(){
    
    })()--- 会被立即调用的函数表达式,作为函数被调用

When called this way, there are two possibilities for the function context (the value of the this keyword): in non-strict mode it will be the global context (the window object), and in strict mode it will be undefined.
2. Called as a method

var ninja = {
    
    };
ninja.skulk = function(){
    
    };
ninja.skulk();

When a function is assigned to a property of an object and the function is invoked through an object property reference, the function is invoked as a method of the object.

When a function is called as a method of an object, the object becomes the context of the function and can be accessed through parameters inside the function.

function whatsMyContext() {
    
      
  return this;
}
var ninja1 = {
    
    
 getMyThis: whatsMyContext 
};

ninja1.getMyThis() === ninja1    // true

var ninja2 = {
    
    
 getMyThis: whatsMyContext
};
ninja2.getMyThis() === ninja2   // true

3. Called as a constructor

function Ninja() {
    
    
 this.skulk = function() {
    
    
   return this;
 }; 
}--- 构造函数创建一个对象,并在该对象也就是函数上下文上添加一个属性skulk。 
这个skulk方法再次返回函数上下文,从而能让我们在函数外部检测函数上下文
var ninja1 = new Ninja();
var ninja2 = new Ninja();--- 通过关键字new调用构造函数创建两个新对象, 
变量ninja1和变量ninja2分别引用了这两个新对象
ninja1.skulk() === ninja1  // true 
ninja2.skulk() === ninja2  // true--- 检测已创建对象中的skulk方法。每个方 法都应该返回自身已创建的对象

Calling a function with the keyword new will trigger the following actions:

1. Create a new empty object.

2. The object is passed to the constructor as the this parameter, thus becoming a function of the constructor

context.

3. The newly constructed object is used as the return value of the new operator.

The purpose of the constructor is to create a new object, initialize it, and then use it as the return value of the constructor

When the constructor has a return value and the return value is a simple data type:

function Ninja() {
    
    --- 定义一个叫做Ninja的构造函数  
  this.skulk = function () {
    
    
   return true;
 };
 return 1;--- 构造函数返回一个确定的原始类型值,即数字1 
}
Ninja() === 1,--- 该函数 以函数的形式被调用,正如预期,其返回值为数字1
var ninja = new Ninja();--- 该函数通过new关键字以构造函数的形式被调用
typeof ninja === "object"
typeof ninja.skulk === "function"--- 测试表明,返回值1被忽略了,一个新的被初始化的对象被通过关键字new所返回

When the constructor has a return value and the return value is an object:

var puppet = {
    
    
 rules: false 
};--- 创建一个全局对象,该对象的rules属性设置为false
function Emperor() {
    
    
 this.rules = true;
 return puppet; 
}--- 尽管初始化了传入的this对象,返回该全局对象
var emperor = new Emperor();--- 作为构造函数调用该函数
emperor === puppet
emperor.rules === false--- 测试表明,变量emperor 的值为由构造函数返回的对象,而不是new表达式所返回的对象

The puppet object is finally used as the return value of the constructor call, and the operation of the function context in the constructor is invalid

Summary:
If the constructor returns an object, the object will be returned as the value of the entire expression, and the this passed into the constructor will be discarded. However, if the constructor returns a non-object type, the return value is ignored and the newly created object is returned
when the constructor is called as a simple function

function Ninja() {
    
       
  this.skulk = function() {
    
         
    return this;
  };
}
var whatever = Ninja();

If called in non-strict mode, the skulk property will be created on the window object,
because this is not defined in strict mode.
4. Use the apply and call method calls
The main differences between different types of function calls are:

The object that is ultimately passed to the executing function as the function context (which can be implicitly referenced by the this parameter) is different.

For methods, it is the object where the method is located;

window or undefined (depending on strict mode) for top-level functions;

For the constructor, it is a newly created object instance.

function juggle() {
    
    
 var result = 0;
 for (var n = 0; n < arguments.length; n++) {
    
    
  result += arguments[n];
 }
 this.result = result; 
}--- 函数“处理”了参数,并将结果result变量放在任意一个作为该函数上下文的对 象上
var ninja1 = {
    
    };
var ninja2 = {
    
    };--- 这些对象的初始值为空,它们会作为测试对象
juggle.apply(ninja1,[1,2,3,4]);--- 使用apply方法向ninja1传递一个参数 数组
juggle.call(ninja2, 5,6,7,8);--- 使用call方法向ninja2传递一个参数列表
ninja1.result === 10
ninja2.result === 26--- 测试展现了传入ju ggle方法中的对象拥有了结果值

The functions of apply and call are similar, but the question is how to choose between the two?

The answer is to choose the method that matches the existing parameters.

If there is an unrelated set of values, use the call method directly. If the existing parameter is an array type, the apply method is a better choice.

5. Arrow functions

Arrow functions do not have a separate this value. The this of an arrow function is the same as the context in which it is declared.

When calling an arrow function, the this parameter is not passed in implicitly, but the context is inherited from the function at the time of definition.

Define the object literal in the global code, define the arrow function in the literal, then this in the arrow function points to the global window object


<button id="test">Click Me! </button>
<script>
 assert(this === window, "this === window");--- 全局代码中的this指向 全局window对象
 var button = {
    
    --- 使用对象字面量定义button
   clicked: false,
   click: () => {
    
    --- 箭头函数是对象字面量的属性
    this.clicked = true;
    assert(button.clicked,"The button has been clicked");--- 验证 是否单击按钮
    assert(this === window, "In arrow function this === window");- -- 箭头函数中的this指向全局window对象
    assert(window.clicked, "clicked is stored in window");---  clicked属性存储在window对象上
  }
 }
  var elem = document.getElementById("test");
  elem.addEventListener("click", button.click);
</script>

6. bind method

All functions can access the bind method, which can create and return a new function and bind it to the incoming
object

insert image description here

Closures and Scope

Without closures, event handling, and animations that include callback functions, their implementation would be much more complicated. Beyond that, without closures, it would be completely impossible to implement private variables.

Closures allow functions to access and manipulate variables outside the function.

A closure enables a function to access variables or functions as long as they exist in the scope in which they are declared.

a simple closure

var outerValue = "ninja";--- 在全局作用域中定义一个变量
function outerFunction() {
    
    
 assert(outerValue === "ninja", "I can see the ninja.");--- 在全局作用域中声明函数
}
outerFunction();--- 执行该函数

We declare the variable outerValue and the outer function outerFunction in the same scope—in this case, the global scope.

Because the outer variable outerValue and the outer function outerFunction are declared in the global scope, this scope (actually a closure) never disappears (as long as the application is running). This is no surprise, the function has access to the outer variable, since it (the outer variable) is still in scope and visible.

var outerValue = "samurai";
var later; 
function outerFunction() {
    
    
 var innerValue = "ninja"; 
  function innerFunction() {
    
    
   assert(outerValue === "samurai", "I can see the samurai.");
   assert(innerValue === "ninja", "I can see the ninja.")   
  }  
  later = innerFunction;  
}
outerFunction(); 
later(); 

    
    

Crucial: ninja variables are still detected despite trying to hide inside the function body.

When an inner function is declared within an outer function, not only is the declaration of the function defined, but a closure is also created. The closure not only contains the declaration of the function, but also contains all variables that were in scope at the time the function was declared. When the inner function is finally executed, although the scope at the time of declaration has disappeared, the original scope can still be accessed through the closure.

Just like the guard bubble, the closure of the inner function keeps the variables in the function's scope for as long as the inner function exists.

This is closure. Closures create a safety bubble of variables and functions in the scope they were defined in, so the function gets what it needs at execution time. The bubble contains functions and variables along with the function itself.
It is very important to remember that every function that accesses variables through a closure has a scope chain, and the scope chain contains all information about the closure. So, while closures are very useful, they should not be overused. When using closures, all information is stored in memory and will not be cleaned up until the JavaScript engine ensures that the information is no longer used (safe for garbage collection) or the page is unloaded

Use of closures

1. Encapsulate private variables

Hides the variable in the constructor, making it inaccessible in the outer scope, but accessible inside the closure.

function Ninja() {
    
    --- 定义Ninja构造函数
   var feints = 0;--- 在构造函数内部声明一个变量,因为所声明的变量的作用域局限于构造函数的内部,
所以它是一个“私有”变量。我们使用该变量统计Ninja佯攻的次数  
   this.getFeints = function() {
    
    
     return feints;--- 创建用于访问计数变量feints的方法。由于在构造函数 
     外部的代码是无法访问feints变量的,这是通过只读形式访问该变量的常用方法
 };
 this.feint = function() {
    
    
   feints++;  
 };--- 为feints变量声明一个累加方法。由于feints为私有变量,在外部是无法累加的,
      累加过程则被限制在我们提供的方法中
}
var ninja1 = new Ninja();--- 现在开始测试,首先创建一个Ninja的实例 
ninja1.feint();--- 调用feint方法,通过该方法增加Ninja的佯攻次数
assert(ninja1.feints === undefined,--- 验证我们无法直接获取该变量值);--- 虽然我 们无法直接对feints变量赋值,但是我们仍然能够通过getFeints方法操作该变量的值
var ninja2 = new Ninja();
assert(ninja2.getFeints() === 0,
    "The second ninja object gets its own feints variable.");---  当我们通过ninja构造函数创建一个新的ninja2实例时,ninja2对象则具有
自己私有的feints变量


2. Callback function

Handling callback functions is another common use case for closures. A callback function refers to a function that needs to be called asynchronously at some point in the future. Usually, in this callback function, we often need to access external data frequently.

<div id="box1">First Box</div>--- 创建用于展示动画的DOM元素
<script>
 function animateIt(elementId) {
    
    
   var elem = document.getElementById(elementId);--- 在动画函数ani mateLt内部,获取DOM元素的引用
   var tick = 0;--- 创建一个计时器用于记录动画执行的次数
   var timer = setInterval(function() {
    
    --- 创建并启动一个JavaScript 内置的计时器,传入一个回调函数
    if (tick < 100) {
    
    
     elem.style.left = elem.style.top = tick + "px";
     tick++;
    }--- 每隔10毫秒调用一次计时器的回调函数,调整元素的位置100else {
    
    
     clearInterval(timer);
     assert(tick === 100,--- 执行了100次之后,停止计时器,并验证我 们还可以看到与执行动画有关的变量
         "Tick accessed via a closure.");
     assert(elem,
         "Element also accessed via a closure.");
     assert(timer,
         "Timer reference also obtained via a closure.");
   }
  }, 10);---  setInterval函数的持续时间为10毫秒,也就是说回调函数每隔10毫秒调用一次
 }  
animateIt("box1");--- 全部设置完成之后,我们可以执行动画函数并查看动画效果
</script>

By defining variables inside the function, and based on the closure, making these variables accessible in the callback function of the timer, each animation can get the private variables in its own "bubble".

If we put the variable in the global scope, then we need to set 3 variables for each animation, otherwise we use 3 variables to track the state of multiple different animations at the same time, and the state of the animation will conflict.
insert image description here

Track code through execution context

The function context can be accessed by keyword when calling the function. Function execution context, although also called context, is a completely different concept.

Execution context is an internal JavaScript concept that JavaScript engines use to track the execution of functions.

There are two kinds of execution contexts: global execution context and function execution context.

The most important difference between the two is: there is only one global execution context, which is created when the JavaScript program starts executing; while a new function execution context is created every time a function is called.

JavaScript is based on a single-threaded execution model: only specific code can be executed at a specific moment. Once a function call occurs, the current execution context must stop execution and create a new function execution context to execute the function. After the function execution is completed, the function execution context is destroyed and returned to the execution context when the call occurred. So you need to keep track of the execution context - the context that is executing and the context that is waiting on. The easiest way to trace is to use the execution context stack (or call stack).
insert image description here
Each JavaScript program only creates one global execution context, and executes from the global execution context (in a single-page application, each page has only one global execution context). In the above process, we constantly create new function execution contexts and place them in The top of the execution context stack.

Only certain pieces of code can be executed at a given moment.

Use the Lexical Environment to track the scope of variables

Inner code structures can access variables defined in outer code structures.

variable type in js

const

We cannot assign a brand new value to a const variable. However, we can modify the object that the const variable already has. For example, we can add properties to existing objects

was

var globalNinja = "Yoshi";
function reportActivity() {
    
    
  var functionActivity = "jumping"; 

  for (var i = 1; i < 3; i++) {
    
    
     var forMessage = globalNinja + " " + functionActivity;  
     assert(forMessage === "Yoshi jumping",
         "Yoshi is jumping within the for block");  
     assert(i, "Current loop counter:" + i);
  }
  assert(i === 3 && forMessage === "Yoshi jumping",
      "Loop variables accessible outside of the loop");--- 但是 在for循环外部,仍然能访问for循环中定义的变量
}
reportActivity();

assert(typeof functionActivity === "undefined"
   && typeof i === "undefined" && typeof forMessage === "undefined",   
      "We cannot see function variables outside of a function");  

Confusingly, even variables defined inside the block scope can still be accessed outside the block scope.

This stems from the fact that variables declared via var are actually always registered within the nearest function or in the global lexical environment, regardless of block-level scope.

Because var is function scoped.

Variables declared via var are defined within the nearest function or in the global lexical environment (block-level scope is ignored). In the above example, although the variables forMessage and i are included in the for loop, they are actually registered in the reportActivity environment (the closest function environment)

Use let and const to define variables with block scope

let and const define variables directly in the nearest lexical environment (be it at block scope, loop, function, or the global environment).

We can use let and const to define variables at block level, function level, and global level.

Register an identifier in the Lexical Environment

If you call this function before the check function is declared, there will be no problem, but how does the code execute line by line, and how does the JavaScript engine know that the check function exists? This shows that the JavaScript engine is playing a trick, and the execution of JavaScript code is actually carried out in two stages.

Once a new lexical environment has been created, the first phase is executed. In the first phase, no code is executed, but the JavaScript engine accesses and registers the variables and functions declared in the current lexical environment. JavaScript starts executing the second phase after the first phase is complete, depending on the type of variable (let, var, const, and function declarations) and the type of environment (global environment, function environment, or block-level scope).

The specific process is as follows:

1. If you are creating a function environment, then create default values ​​for formal parameters and function parameters. This step will be skipped if it is a non-functional environment.

2. If a global or function environment is created, the current code will be scanned for function declarations (the function bodies of other functions will not be scanned), but function expressions or arrow functions will not be scanned. For the function declaration found, the function is created and bound to the current environment's identifier with the same name as the function. If the identifier already exists, the value of the identifier will be overwritten. This step is skipped in case of block scope.

3. Scan the current code for variable declaration. In a function or global environment, finds all variables declared with var outside the current function and other functions, and finds all variables defined with let or const outside other functions or code blocks. In a block-level environment, only variables defined via let or const in the current block are looked up. For the found variable, if the identifier does not exist, register it and initialize it to undefined. If the identifier already exists, its value will be retained.

The whole process is shown in the figure below:

insert image description here

Why can a function be called before the function declaration

The reason we can do this is that fun is defined through a function declaration, and the second stage shows that the function has been defined through a function declaration, and the function identifier is registered when the current lexical environment is created before other code execution.

The JavaScript engine provides convenience for developers in this way, allowing us to use function references directly without forcing the definition order of the specified functions.

It should be noted that this case is only valid for function declarations. Function expressions and arrow functions are not in this process, but are defined during program execution. This is why function expressions and arrow functions cannot be accessed ahead of time.

function overloading

assert(typeof fun === "function", "We access the function");  
var fun = 3;  
assert(typeof fun === "number", "Now we access the number");  
function fun(){
    
    } 
assert(typeof fun === "number", "Still a number");---  fun 仍然指向数字

In the above example, both the declared variable and the function use the same name fun. If you execute this code, you will find that both assertions pass.

In the first assertion, the identifier fun points to a function; in the second assertion, the identifier fun points to a number.

This behavior of JavaScript is a direct result of identifier registration.

In the second step of the process, the function defined by the function declaration creates the function before the code is executed and assigns it to the corresponding identifier;

In step 3, variable declarations are processed, and those variables that are not declared in the current environment will be assigned the value undefined.

In the above example, in step 2 - when registering the function declaration, because the identifier fun already exists, it is not assigned the value undefined. This is why the first assertion to test whether fun is a function passes.

After that, the assignment statement var fun = 3 is executed, and the number 3 is assigned to the identifier fun. After executing this assignment statement, fun no longer points to the function, but points to the number 3.

During the actual execution of the program, the function declaration part is skipped, so the declaration of the function will not affect the value of the identifier fun.

generator function

function* WeaponGenerator() {
    
    --- 通过在关键字function后面添加星号*定义 生成器函数
 yield "Katana";
 yield "Wakizashi";
 yield "Kusarigama";--- 使用新的关键字yield生成独立的值 
}
for (let weapon of WeaponGenerator()) {
    
      
  console.log(weapon !== undefined, weapon); 
}--- 使用新的循环类型for-of取出生成的值序列

// 打印结果如下
true Katana
true Wakizashi
true Kusarigama

Generator functions are very different from standard functions. For starters, calling a generator doesn't execute the generator function; instead, it creates an object called an iterator.

function* WeaponGenerator() {
    
    
  yield "Katana";
  yield "Wakizashi"; 
} 
  //  ⇽--- 定义一个生成器,它能生成一个包含两个武器数据的序列
const weaponsIterator = WeaponGenerator(); 
//  ⇽--- 调用生成器得到一个迭代器 ,从而我们能够控制生成器的执行
const result1 = weaponsIterator.next(); 
//  ⇽--- 调用迭代器的next方法向生成器请求一个新值
assert(typeof result1 === "object"
   && result1.value === "Katana"
   && !result1.done,
   "Katana received!");  
    // ⇽--- 结果为一个对象,其中包含着一个返回值,及一个指示器告诉我们生成器是否还会生成值
const result2 = weaponsIterator.next(); 
assert(typeof result2 === "object"
   && result2.value === "Wakizashi" && !result2.done,
   "Wakizashi received!"
   );  
    // ⇽--- 再次调用next方法从生成器中获取新值
const result3 = weaponsIterator.next(); 
assert(typeof result3 === "object"
   && result3.value === undefined && result3.done,
   "There are no more results!"
   );   
  //  ⇽--- 当没有可执行的代码,生成器就会返回“undefined”值,表示它的状态为已经完成

Looking at the implementation principle of for-of from iterator traversal:

function* WeaponGenerator(){
    
     
 yield "Katana";
 yield "Wakizashi";
}
const weaponsIterator = WeaponGenerator();--- 新建一个迭代器
let item;--- 创建一个变量,用这个变量来保存生成器产生的值 
while(!(item = weaponsIterator.next()).done) {
    
    
 assert(item !== null, item.value); 
}--- 每次循环都会从生成器中取出一个值,然后输出该值。当生成器不会再生成值 的时候,停止迭代

while(!(item = weaponsIterator.next()).done) {
    
      
  assert(item !== null, item.value)
}

A for-of loop is nothing but syntactic sugar for iterating over an iterator.

Pass execution to the next generator

function* WarriorGenerator(){
    
    
 yield "Sun Tzu";
 yield* NinjaGenerator();--- yield*将执行权交给了另一个生成器  
 yield "Genghis Khan";
}
function* NinjaGenerator(){
    
      
 yield "Hattori";
 yield "Yoshi";
}
for(let warrior of WarriorGenerator()){
    
     
  assert(warrior !== null, warrior);
}

After executing this code, it will output Sun Tzu, Hattori, Yoshi, Genghis Khan.

Using the yield* operator on an iterator, the program will jump to another generator for execution. In this example, the program jumps from WarriorGenerator to a new NinjaGenerator generator. Every time the next method of WarriorGenerator returning iterator is called, the execution will be re-addressed to NinjaGenerator. The generator will hold execution until there is no more work to do. So in our example, Hattori and Yoshi are followed by Sun Tzu. Only after the NinjaGenerator's work is complete, calling the original iterator will continue to output the value Genghis Khan.

Note that this is all transparent to the code that called the original iterator.

The for-of loop doesn't care that WarriorGenerator delegates to another generator, it only cares about calling the next method until the done state arrives.

use generator

1. Use a generator to generate an ID sequence

function* IdGenerator() {
    
    --- 定义生成器函数IdGenerator
 let id = 0;--- 一个始终记录ID的变量,这个变量无法在生成器外部改变  while (true) {
    
    
   yield ++id;
 }--- 循环生成无限长度的ID序列
}
const idIterator = IdGenerator();--- 这个迭代器我们能够向生成器请求新的 IDconst ninja1 = {
    
     id: idIterator.next().value };
const ninja2 = {
    
     id: idIterator.next().value };
const ninja3 = {
    
     id: idIterator.next().value };--- 请求3个新IDassert(ninja1.id === 1, "First ninja has id 1");
assert(ninja2.id === 2, "Second ninja has id 2");
assert(ninja3.id === 3, "Third ninja has id 3");--- 测试运行结果

Generally, you should not write infinite loop code in standard functions. But no problem in the generator! When the generator encounters a yield statement, it will suspend execution until the next method is called, so only each time the next method is called, the while loop will iterate once and return to the next ID value.
2. Use iterators to traverse the DOM tree

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="subTree"> 
     <form>
       <input type="text"/>  
     </form> 
    <p>Paragraph</p> 
    <span>Span</span>
  </div>
  <script>

function* DomTraversal(element){
    
     
   yield element;
   element = element.firstElementChild; 
   while (element) {
    
    
    yield* DomTraversal(element);  
  // ⇽--- 用yield将迭代控制转移到另一个Dom Traversal生成器实例上
    element = element.nextElementSibling;
   }
 }
const subTree = document.getElementById("subTree");  
   // 打印出来当前element的标签名称
for(let element of DomTraversal(subTree)) {
    
      
   console.log(element !== null, element.nodeName); 
}
</script>
</body>
</html>

Send values ​​as generator function arguments

function* NinjaGenerator(action) {
    
      
  // ⇽--- 生成器可以像其他函数一样接收标准参数
  const imposter = yield ("Hattori " + action);  
  // ⇽--- 奇迹出现了。产生一个值的同时,生成器会返回一个中间计算结果。
  // 通过带有参数的调用迭代器的next方法,我们可以将数据传递回生成器
 console.log(imposter === "Hanzo",
      "The generator has been infiltrated");
  yield ("Yoshi (" + imposter + ") " + action);   
  // ⇽--- 传递回的值将成为yield表达式的返回值,因此impostrer的值是Hanzo
 }

 const ninjaIterator = NinjaGenerator("skulk"); 
 const result1 = ninjaIterator.next();
 console.log(result1.value === "Hattori skulk","Hattori is skulking"); 
 const result2 = ninjaIterator.next("Hanzo");
 console.log(result2.value === "Yoshi (Hanzo) skulk",
     "We have an imposter!");  

Throw an exception

function* NinjaGenerator() {
    
    
 try{
    
    
   yield "Hattori";
   fail("The expected exception didn't occur");
   // ⇽--- 此处的错误将不会发生
}
catch(e){
    
    
  assert(e === "Catch this!", "Aha! We caught an exception");  
  // ⇽--- 捕获异常并检测接收到的异常是否符合预期
 }
}
const ninjaIterator = NinjaGenerator();
const result1 = ninjaIterator.next();
assert(result1.value === "Hattori", "We got Hattori"); 
// ⇽--- 从生成器拉取一个值
ninjaIterator.throw("Catch this!"); 
// ⇽--- 向生成器抛出一个异常

Exploring the internals of generators (page 187)

insert image description here

After the program has finished executing from the generator, an interesting phenomenon occurs. Generally, when the program returns from a standard function, the corresponding execution environment context will be popped from the stack and completely destroyed. But not in generators.
insert image description here
The corresponding NinjaGenerator will be popped from the stack, but since the ninjaIterator still holds a reference to it, it will not be destroyed.

insert image description here
As shown above, if this is just an ordinary function call, this statement will create a new next() execution environment context item and put it on the stack. But you may have noticed that generators are by no means standard and behave very differently for next method calls. It reactivates the corresponding execution context. In this case, the NinjaGenerator context, and puts that context on top of the stack, continuing where it left off.

Calling a generator's next method reactivates the item on the execution context stack corresponding to that generator, pushing that item onto the stack first, and then continuing execution from where it left off.

insert image description here
insert image description here
At this point, we go through the whole process again: first activate the context reference of NinjaGenerator through ninjaIterator, push it onto the stack, and continue execution at the position where we left last time. In this example, the generator evaluates the expression "Yoshi " + action. But this time instead of encountering a yield expression, we encounter a return statement. This statement will return the value Yoshi skullk and end the execution of the generator, and the generator enters the end state.

The characteristics of the generator
When we get control from the generator, the execution environment context of the generator is always saved, not destroyed after exiting like a standard function.

use promises

Promise objects are used as placeholders for the results of asynchronous tasks. It represents a value that we have not obtained yet but hope to obtain in the future.

insert image description here
insert image description here
The Combination of Generators and Promises

async(function*(){
    
     
 try {
    
    
  const ninjas = yield getJSON("data/ninjas.json");
  const missions = yield getJSON(ninjas[0].missionsUrl);
  const missionDescription = yield getJSON(missions[0].detailsUrl);   

 }
 catch(e) {
    
    
  //Oh no, we weren't able to get the mission details  
 } 
});
          
function async(generator) {
    
     
 var iterator = generator(); 
 function handle(iteratorResult) {
    
     
  if(iteratorResult.done) {
    
     
    return; 
  }  
  const iteratorValue = iteratorResult.value;
  if(iteratorValue instanceof Promise) {
    
    
   iteratorValue
     .then(res => handle(iterator.next(res)))
     .catch(err => iterator.throw(err));   
  }
 }
 try {
    
    
  handle(iterator.next());
 }
 catch (e) {
    
     
   iterator.throw(e); 
 } 
}

Object-Oriented and Prototype

insert image description here

insert image description here

Each object can have a prototype, each object's prototype can also have a prototype, and so on, forming a prototype chain. Finding specific properties will be delegated up the entire prototype chain, and only stop when there are no more prototypes to look up.

insert image description here

insert image description here
insert image description here
Even though the Ninja functions no longer point to the old Ninja prototype, the old prototype still exists in the ninja1 instance, and the swingSword methods are still accessible through the prototype chain. However, if we create a new instance object after these changes in Ninja, the state of the application at this time is shown in Figure 7.10.
insert image description here
The reference relationship between the object and the function prototype is established when the object is created. The newly created object will refer to the new prototype, and it can only access the pierce method. The original old object keeps the original prototype, and can still access the swingSword method.

Implementing object types through constructors

Check the type of the instance and its constructor

function Ninja(){
    
    }
const ninja = new Ninja();
assert(typeof ninja === "object", 
  // ⇽--- 通过typeof检测ninja的类型,但从结 果仅仅能够得知ninja是一个对象而已
    "The type of the instance is object."
);
assert(ninja instanceof Ninja,  
  // ⇽--- 通过instanceof检测ninja的类型,其结 果提供更多信息——ninja是由Ninja构造而来的
    "instanceof identifies the constructor." 
); 
assert(ninja.constructor === Ninja,  
   // ⇽--- 通过constructor引用检测ninja 的类型,得到的结果为其构造函数的引用
    "The ninja object was created by the Ninja function."
);

instanceof, which provides a method for detecting whether an instance was created by a particular constructor.

We can use the constructor attribute, all instance objects can access the constructor attribute, and the constructor attribute is a reference to the function that creates the instance object.
The constructor attribute exists only to indicate where the object was created from.

implement inheritance

What we really want to achieve is a complete prototype chain. On the prototype chain, Ninja inherits from Person, Person inherits from Mammal, Mammal inherits from Animal, and so on, all the way to Object. The best technical solution for creating such a prototype chain is that the prototype of an object is directly an instance of another object:

SubClass.prototype = new SuperClass();

 Ninja.prototype = new Person();

Because the prototype of the SubClass instance is an instance of SuperClass, the SuperClass instance has all the properties of SuperClass, and the SuperClass instance also has a prototype pointing to the superclass.
Inheritance using prototypes

function Person(){
    
    }
Person.prototype.dance = function(){
    
    };
function Ninja(){
    
    }
Ninja.prototype = new Person();  
// ⇽--- 通过将Ninja的原型赋值为Person的实例 ,实现Ninja继承Person
const ninja = new Ninja();
assert(ninja instanceof Ninja,
    "ninja receives functionality from the Ninja prototype"); 
assert(ninja instanceof Person, "... and the Person prototype"); 
assert(ninja instanceof Object, "... and the Object prototype"); 
assert(typeof ninja.dance === "function", "... and can dance!")

insert image description here

insert image description here

When executing the ninja instanceof Ninja expression, the JavaScript engine checks whether the prototype of the Ninja function, the new Person() object, exists on the prototype chain of the ninja instance. The new Person() object is the prototype of the ninja instance, so the expression evaluates to true.
When checking for ninja instanceof Person, the JavaScript engine looks up the prototype of the Person function, checking to see if it exists on the ninja instance's prototype chain. Since the prototype of Person does exist on the prototype chain of the ninja instance, Person is the prototype of the new Person() object, so Person is also the prototype of the ninja instance.

insert image description here

The instanceof operator checks whether the function prototype on the right exists on the prototype chain of the object on the left of the operator. Be careful that the prototype of a function can change at any time.
Instances do not have access to static methods whereas classes have access to static methods.

Implementing inheritance in versions prior to ES6 was a pain.

function Person(){
    
    }
Person.prototype.dance = function(){
    
    };
function Ninja(){
    
    } 
Ninja.prototype = new Person();
Object.defineProperty(Ninja.prototype, "constructor", {
    
       
  enumerable: false,
  value: Ninja,
  writable: true
});

Methods that are accessible to all instances must be added directly on the constructor prototype, such as the dance method on the Person constructor. In order to achieve inheritance, we must set the prototype derived from the instance object as the "base class".
In this example, we assign a new Person instance object to Ninja.prototype. Unfortunately, this messes up the constructor property, so it needs to be set manually via the Object.defineProperty method.

control object access

insert image description here

Use proxy to detect performance (new proxy proxy function)

function isPrime(number){
    
    
  if(number < 2) {
    
     return false; }
  for(let i = 2; i < number; i++) {
    
    
    if(number % i === 0) {
    
     return false; }  
  }  
  // ⇽--- 定义isPrime函数的简单实现
 return true; 
}
isPrime = new Proxy(isPrime, {
    
     
  // ⇽--- 使用代理包装isPrime方法
 apply: (target, thisArg, args) => {
    
     
   // ⇽--- 定义apply方法,当代理对象作为 函数被调用时将会触发该apply方法的执行  
   console.time("isPrime"); 
   // ⇽--- 启动一个计时器,记录isPrime函数执行的起 始时间
  const result = target.apply(thisArg, args);  
   // ⇽--- 调用目标函数   
  console.timeEnd("isPrime"); 
   // ⇽--- 停止计时器的执行并输出结果
  return result;  }
});
isPrime(1299827);  
// ⇽--- 同调用原始方法一样,调用isPrime方法

Use the isPrime function as the proxy's target object. At the same time, add the apply method, which will be called when the isPrime function is called.

Auto-populate properties with proxies

function Folder() {
    
    
  return new Proxy({
    
    }, {
    
    
   get: (target, property) => {
    
    
     report("Reading " + property);  
     // ⇽--- 记录所有读取对象属性的日志
     if(!(property in target)) {
    
    
        target[property] = new Folder();      
     }--- 如果对象不具有该属性,则创建该属性
    return target[property];    
   }
 });
}
 const rootFolder = new Folder();
  try {
    
    
   rootFolder.ninjasDir.firstNinjaDir.ninjaFile = "yoshi.txt";  
  // ⇽--- 每 当访问属性时,都会执行代理方法,若该属性不存在,则创建该属性
   pass("An exception wasn’t raised"); 
// ⇽--- 不会抛出异常
}
catch(e){
    
    
  fail("An exception has occurred");
}

Negative array indexing using proxies

function createNegativeArrayProxy(array) {
    
    
  if (!Array.isArray(array)) {
    
    
   throw new TypeError('Expected an array');   
    // ⇽--- 如果传入的参数不是数组,则抛出异常
 }
  return new Proxy(array, {
    
     
    // ⇽--- 返回新的代理。该代理使用传入的数组作为 代理目标
     get: (target, index) => {
    
      
      // ⇽--- 当读取数组元素时调用get方法     
     index = +index; 
     // ⇽--- 使用一元+操作符将属性名变成的数值
     return target[index < 0 ? target.length + index : index]; 
     // ⇽--- 如果访问的是负向索引,则逆向访问数组。如果访问的是正向索引,则正常访问数组    
  },
   set: (target, index, val) => {
    
     
     // ⇽--- 当写入数组元素时,调用set方法    
    index = +index;
    return target[index < 0 ? target.length + index : index] = val;    
   }
  }
 ); 
}

const ninjas = ["Yoshi", "Kuma", "Hattori"]; 
// ⇽--- 创建标准数组
const proxiedNinjas = createNegativeArrayProxy(ninjas); 
// ⇽--- 将数组传 入create-Nigati-veArrayProx-y,创建代理数组
assert(ninjas[0] === "Yoshi" && ninjas[1] === "Kuma"
       && ninjas[2] === "Hattori"
,"Array items accessed through positive indexes");


assert(typeof ninjas[-1] === "undefined" 
       // ⇽--- 验证无法通过标准数组直接使 用负向索引访问数组元素。
   && typeof ninjas[-2] === "undefined"
   && typeof ninjas[-3] === "undefined",
     "Items cannot be accessed through negative indexes on an array");
assert(proxiedNinjas[-1] === "Hattori" 
       // ⇽--- 但是可以通过代理使用负向索引 访问数组元素,因为代理get方法进行了必要的处理
   && proxiedNinjas[-2] === "Kuma"
   && proxiedNinjas[-3] === "Yoshi",
    "But they can be accessed through negative indexes");

Proxies are not efficient, so use with caution.

processing collections

The pop and push methods affect only the last element of the array: pop removes the last element, and push adds elements to the end of the array. The shift and unshift methods modify the first element, and the index of each element after that needs to be adjusted. Therefore, the pop and push methods are much faster than the shift and unshift methods, and the shift and unshift methods are not recommended for non-special cases.

reduce

const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((aggregated, number) =>                 
    aggregated + number, 0);  
// ⇽--- 使用reduce函数从数组中取得累计值
 assert(sum === 10, "The sum of first four numbers is 10");

The reduce method receives the initial value, executes the callback function for each element of the array, and the callback function receives the last callback result and the current array element as parameters. The result of the last callback function is used as the result of reduce.

event loop

All microtasks are executed before the next render because their goal is to update the application state before rendering.
Let's imagine the following, the main thread JavaScript code takes 15ms to execute. The first click event handler takes 8ms to run. The second click event handler takes 5ms to run. Let's continue our imagination and imagine that a quick user clicks the first button 5ms after the code executes, and then clicks the second button 12ms later.

insert image description here
Since JavaScript is based on a single-threaded execution model, clicking the firstButton will not immediately execute the corresponding handler. (Remember, once a task starts executing, it will not be interrupted by another task.) The firstButton's event handler enters the task queue, waiting to be executed. Something similar happens when the secondButton is clicked: the corresponding event handler is queued, waiting to be executed. Note that event monitoring and adding tasks are independent of the event loop, although the main thread is still executing, it is still possible to add tasks to the queue.

Instances with both microtasks and macrotasks

insert image description here

Event loop tasks represent actions performed by the browser. Tasks are divided into the following two categories.
Macro tasks are discrete, independent browser operations such as creating the main document object, handling various events, changing URLs, etc.
Microtasks are tasks that should be executed as soon as possible. Includes promise callbacks and DOM mutations.

Due to the single-threaded execution model, only one task can be processed at a time, and the execution of one task cannot be interrupted by another task. An event loop usually has at least two event queues: a macrotask queue and a microtask queue.

Guess you like

Origin blog.csdn.net/m0_57307213/article/details/126997178