directory
First, this point - general case - manual change this point - native JS implementation call, apply, bind 2, memory management mechanism - garbage collection - reference counting - tag clearance - V8 garbage collection algorithm 3, closure - closure caused problems - Postscript to the application of closuresCopy the code
preface
This article is an original article, ten thousand words long, it is recommended to collect after reading. The book goes back to the core JS syntax (the first part), and continues to go deep into JavaScript. If this article has helped you, please bookmark it, like it, comment on it, or reprint it with credit.
A. this b. this C. this D. this
The general situation
In most cases, this is bound at run time, so the point to this depends on how the call is made.
There are roughly the following three cases:
1. Globally in the browser, the global environment directly access this, this always points to the window.
Inside a function, this points to whoever called the method.
Here’s an example
function func() {
return this;
}
console.log(func() === window); // true
const obj = {
func,
};
console.log(obj.func() === obj); // true
Copy the code
In this example, func returns this directly
- If func is executed directly in the global scope, this refers to window.
- When we call func through obj, this refers to the caller, which is obj here.
In global scope, when a function is called directly, the reference to this within the function is different in strict mode than in non-strict mode.
The code:
"use strict";
function strictFunc() {
return this;
}
console.log(strictFunc() === window); // false
console.log(strictFunc() === undefined); // false
Copy the code
- In strict mode, this in strictFunc refers not to window, but to undefined.
- In non-strict mode, as with the func function in the previous example, this always points to window.
Note: The arrow function does not create its own this, but inherits this one level up the scope chain.
3. The reference to this in the class is basically the same as the reference to this in the function. The only difference is that static attributes in the class are not added to this.
The following is a small chestnut on MDN
class Example {
constructor() {
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto)); // ["constructor", "first", "second"]
}
first() {
return this;
}
second() {}
static third(){}}const instance = new Example();
console.log(instance.first() === instance); // true
Copy the code
Manually change the point to this
To manually change the reference to this, use the call, apply, and bind methods.
Similarities:
- Is used to change the direction of this
- The first argument is the object this refers to, which is the context you want to specify
- Can be passed using subsequent parameters
Difference:
- When call is passed, the incoming parameters of the target function are passed one by one, and the target function is executed immediately
- When the apply argument is passed as an array, the target function is immediately executed as well
- The bind argument is passed one by one, but only changes the this point and returns a new function. The target function is not executed
var saying = "hello world";
function say() {
console.log(`say The ${this.saying}`);
}
const dog = {
saying: "bark"};const bull = {
saying: "moo"}; say();// say hello world
say.call(dog); // say bark
say.apply(bull); // say moo
const dogSay = say.bind(dog);
dogSay(); // say bark
Copy the code
A small chestnut to understand the simple
Native JS implementation of call, apply, bind
In the previous section, we’ve seen the differences between call, apply, and bind. Let’s try implementing these three methods ourselves
First, all three methods are called directly from the Function, so they must be mounted on the Function’s prototype.
Function.prototype.call = function () {};
Function.prototype.apply = function () {};
Function.prototype.bind = function () {};
Copy the code
Then, the first entry to each of the three methods is the object to which you want to specify this
Function.prototype.call = function (thisArg) {
thisArg.func = this;
};
Function.prototype.apply = function (thisArg) {
thisArg.func = this;
};
Function.prototype.bind = function (thisArg) {
thisArg.func = this;
};
Copy the code
We have a custom func method inside thisArg that points to the target function
Call call is implemented by passing the parameters one by one. Here we use the extension operator
Function.prototype.call = function (thisArg, ... args) {
thisArg.func = this; thisArg.func(... args);// Destroy after execution
delete thisArg.func;
};
Copy the code
2. Apply is done through the array, this is easy
Function.prototype.apply = function (thisArg, args = []) {
thisArg.func = this;
thisArg.func(args);
// Destroy after execution
delete thisArg.func;
};
Copy the code
3. Bind is a new function that can be invoked by the outside world instead of the target function
Function.prototype.bind = function (thisArg, ... args) {
thisArg.func = this;
return function () { thisArg.func(... args);delete thisArg.func;
};
};
Copy the code
Second, memory management mechanism
The life cycle of memory is the same regardless of the language, right
- Allocate as much memory as you need
- Use allocated memory (read, write)
- Release it when it is no longer needed
In JS, memory is allocated when a variable is defined. When the program finds that the variable is no longer in use, it is automatically freed.
Js memory space is divided into stack, heap
- Stack: small space for execution context, which in turn contains references to both basic and complex data types
- Heap: large space for complex data types (note: the replication of basic data types is full replication, while the replication of complex data types only copies the reference address)
For memory in the stack, the operating system allocates and releases it automatically. As for the memory in the heap, the size of each block is not fixed, so the operating system cannot complete automatic release, which requires js engine to manually release.
All the JS engine has to do is find memory that is no longer needed and free it. Here involves the JS engine garbage collection mechanism.
The garbage collection
Every once in a while, the JS garbage collector will “inspect” the memory. When it determines that a chunk of memory is no longer needed, it frees it up, a process called garbage collection
How to find the memory space that is not needed becomes the focus of garbage collection mechanism. There are two common garbage collection algorithms:
- Reference counting
- Mark clearance
Reference counting
Reference counting, as the name implies, is done by counting the number of references to an area of memory. A value of 0 indicates that the area of memory is not required and can be reclaimed.
“Reference” is used only to describe the memory address of the reference type.
In the context of memory management, when an object has access (implicit or explicit) to another object, it is called an object reference to another object. For example, a Javascript object has a reference to its prototype (implicit reference) and a reference to its properties (explicit reference). The concept of “object” here refers not only to JavaScript objects, but also to function scopes (or global lexical scopes)
See through a little chestnut
const a = { name: "MelonField".author: "HLianfa" };
Copy the code
In JavaScript, assignment expressions are read from right to left. First, a block of memory is created for the object on the right. And then the a variable points to it; This creates a “reference” to the object. The object’s reference count is then equal to 1.
a = null;
Copy the code
{name: ‘MelonField’, author: ‘HLianfa’} the reference count of the object becomes zero, which means it is no longer needed, and the next time the garbage collector “inspects” the object, it frees up the memory.
Defects in reference counting
Reference counting, one of the most rudimentary garbage collection algorithms, is now largely obsolete because of a fatal flaw: “it can’t handle instances of circular references.” This may result in occupied memory that will never be released, causing a memory leak.
function run() {
var a = {};
var b = {};
a.link = b;
b.link = a;
}
run();
Copy the code
In this example, we finally run run; After the function is executed, all the memory used by the variables in the function can be freed. However, in an environment where the reference counting algorithm is used as the garbage collection algorithm, the memory occupied by variables A and B cannot be freed.
As shown in the figure, a and B are rolled up, they refer to each other, and “circular references” cannot be handled by the reference counting algorithm, so they remain in memory and cannot be freed.
Mark clearance
Since 2012, all major browsers have switched to tag-clearing algorithms because of a “bug” in reference counting.
The tag removal algorithm, as the name suggests, is to mark out, and then clear.
-
Mark: Start from the pointer of the original root object, which is the global object (browser: window, Node: global), and search down the child nodes. The child nodes that are searched are marked “reachable” (not reachable duck, but reachable).
-
Clear: After all child nodes have been traversed, nodes that are not marked, that is, not referenced anywhere, can be reclaimed.
Garbage collection algorithm in V8
It’s back to V8, but it’s all on Google dad’s watch. In order to improve the recovery efficiency of V8, the heap is further divided into new generation and old generation.
- Cenozoic: Apply to a short-lived object. Be Scavenge
- Old generation: Long lived objects stored by mark-sweep & Mark-compact
Operating mechanism:
- Scavenge further divides the generation into from-space and to-space. It also precedes the application, and copies reachable objects from from-space to to-space, where they are arranged in order. After the remaining objects in from-space are released, the objects in to-space are copied over
- There are two kinds of old generation garbage collection mechanism algorithms:
- Mark-sweep: A routine Sweep of “reachable” objects marked and then “unreachable” objects swept
- Mark-compact: Added object cleaning phase compared to Mark-Sweep, where all “reachable” objects are moved to one end, and memory outside the boundary is cleaned when the move is complete
Third, the closure
First of all, what is a closure?
Definition on kangkang MDN:
A function is bound with a reference to its surrounding state (lexical environment). Such a combination is a closure.
In colloquial terms, a function and the lexical context in which it is declared form a closure.
(If you have forgotten the lexical context, please refer to the execution context above to familiarize yourself with it.)
One of the most common closures is function nesting
function makeFunc() {
var name = "Mozilla";
function displayName() {
console.log(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc(); // Mozilla
Copy the code
In this example, the name can still be printed after the makeFunc is finished.
The reason, as most articles on the web will tell you, is that JS uses lexical scope. This is also true, but it makes more sense to use closures to explain the phenomenon.
- First, myFunc is a reference to an instance of the displayName function.
- Second, an instance of displayName also maintains a reference to the lexical context in which it is declared.
- The lexical context of the displayName declaration contains the name variable.
- So, when myFunc is called, the variable name is still available.
Problems caused by closures
Memory leak?
Many people reflexively think of memory leaks when they think of closures.
However, closures do not cause memory leaks. The cause of the memory leak is the code writing is not standardized.
This is mostly due to the fact that the garbage collection mechanism of early browsers still used reference counting, resulting in circular references.
performance
Closures don’t leak memory, but when used in the wrong places they can affect performance.
function animal(something) {
this.say = function () {
console.log(`say ${something}`);
};
}
var dog = new animal("bark");
var bull = new animal("moo");
dog.say(); // say bark
bull.say(); // say moo
Copy the code
In this example, dog and bull are examples of animal; When you create a dog, the say method is assigned, and when you create a bull, the say method is assigned again. Every time a new animal instance is created, the value of “say” is assigned once. In this example, closures are not a convenience, but a performance disadvantage.
The ideal way is as follows
function animal(something) {
this.saying = something;
}
animal.prototype.say = function () {
console.log(`say The ${this.saying}`);
};
var dog = new animal("bark");
var bull = new animal("moo");
dog.say(); // say bark
bull.say(); // say moo
Copy the code
Application of closures
1. Anti-shaking and throttling
Shaking and throttling limit the number of calls to frequently called functions to optimize front-end performance and experience.
- Anti-shaking: Firing the same function multiple times over a short period of time, only the last time, or only at the beginning
- Throttling: Allows functions to execute only once at a time
/ / image stabilization
function debounce(fn, delay) {
let timer = null;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
if(! timer) { timer =setTimeout(function () { fn.apply(context, args); }, delay); }}; }Copy the code
👆 This example is a simple implementation of anti-shaking. Fn is the function that actually needs to be executed. Debounce clears the timer each time it is called, and the last timer triggered is retained and fn is executed after delay milliseconds.
/ / throttling
function throttle(fn, delay) {
let timer = null;
return function () {
const context = this;
const args = arguments;
if(! timer) { timer =setTimeout(function () {
fn.apply(context, args);
timer = null; }, delay); }}; }Copy the code
👆 this example is also the use of timer, to achieve the throttle; Fn is the function that actually needs to be executed. The first time fn is called, it is not executed immediately. Instead, fn is executed after delay milliseconds, and then every delay millisecond after fn is executed.
2. Simulate private properties
In most programming languages, there is support for privatizing variable methods in classes. In JS, it is not possible to define private variables directly, either through constructors or through class syntax sugar, but it is easy to do so through closures.
const Animal = (function () {
let _saying = "";
class Animal {
constructor(saying) {
_saying = saying;
}
say() {
console.log(`say ${_saying}`); }}returnAnimal; }) ();const dog = new Animal("bark");
dog.say(); // say bark
console.log(dog._saying); // undefined
Copy the code
👆 In this example, we wrap the Animal class with an immediately-executed function that returns. When we access _saying outside of Animal, we get undefined, successfully privatizing _saying. (The chrome 74+ version implements the ES2020 experimental draft with the # defining private properties, which will not be expanded here)
3. Partial functions/currying
- In a nutshell, currie means changing a function that takes multiple arguments to a function that takes only one argument.
- Partial functions are less rigid than Curryization, allowing you to take a few arguments, return a new function, and then accept the rest of the arguments.
function animal(name, color, size, age) {
// do something
}
animal("dog"."yellow"."big"."2");
animal("dog"."yellow"."big"."5");
animal("dog"."yellow"."big"."3");
Copy the code
👆 In this example, we are all working on the “big yellow dog”, the only difference between them is the age, so we can lock the “big yellow dog” by currying.
function animal(name) {
return function (color) {
return function (size) {
return function (age) {
// do something
};
};
};
}
const dog = animal("dog"); // Call once to lock the dog
const yellowDog = dog("yellow"); // Lock the yellow dog
const bigYellowDog = yellowDog("big"); // Lock the big yellow dog
// Const bigYellowDog = animal('dog')('yellow');
bigYellowDog("2");
bigYellowDog("5");
bigYellowDog("3");
Copy the code
Let’s take a look at animal after the partial function.
function animal(name, color, size) {
return function (age) {
// do something
};
}
const bigYellowDog = animal("dog"."yellow"."big");
bigYellowDog("2");
bigYellowDog("5");
bigYellowDog("3");
Copy the code
Which is better or worse depends on the particular business, but concerns about nesting too many layers can be simplified through the factory model.
Afterword.
If you have any other comments, please feel free to discuss them in the comments section. At the same time, the article is posted in the personal public account, welcome to pay attention to MelonField in-depth JS core syntax (next part)
reference
- Developer.mozilla.org/zh-CN/docs/…
- Developer.mozilla.org/zh-CN/docs/…
- Github.com/yacan8/blog…