You don't know
1, Scope and closure
1. Compilation Principle
Generally, compilation is divided into three steps:
a. Word segmentation and lexical analysis (Tokenizing/Lexing)
This process will the entire code(Character string)Break down into meaningful code(For programming languages),These code blocks are called lexical units(token).
For example: code block var a = 2; This program is usually broken down into the following lexical units: VaR, a, =, 2;. Whether spaces are treated as lexical units depends on whether spaces have meaning in the language.
b. Parsing / parsing
This process is to stream lexical units(array)It is converted into a number composed of nested elements, which represents the syntax structure of the program. This number is called"Abstract syntax tree"(Abstract Syntax Tree,AST)
For example: var a = 2; In the abstract syntax tree of, AST as shown in the following figure may be generated:
c. Code generation
This process will AST The process of converting to executable code is closely related to the language and target platform.
Regardless of the specific details, var a = 2 in some way; AST is converted into a set of machine instructions to create a variable called a (including memory allocation), and then 2 is stored in it.
2. Understand the scope
There are three actors to understand when interpreting Code:
Engine: responsible for the compilation and execution of the whole JavaScript program from beginning to end.
Compiler: responsible for syntax analysis and code generation.
Scope: responsible for collecting and maintaining a series of queries composed of all declared identifiers (variables) (generation of scope chain).
3. Lexical scope
Lexical scope means that the scope is determined by the declaration position of variables when writing code. Therefore, when the lexical analyzer processes the code, it will keep the scope unchanged (in most cases). Under special circumstances, modifying or affecting the lexical scope will lead to performance degradation.
a. Search
Identifiers with the same name can be defined in multi-layer nested scopes, which is called "shielding effect", and the achievement of this effect depends on the scope chain
b. Deceptive morphology
Use eval(..) Function, which is used to dynamically execute the created code. When the variable declaration appears in the modified code, the original lexical scope will be changed, similar to setTimout(..) The benefits of the first parameter (when writing string) and the second parameter of newFunction (when writing string) cannot offset the performance loss.
For example:
var a = 10; function fn(str){ eval(str) // Modified the original lexical scope console.log(a); // 20 } fn('var a = 20;')
Use the with keyword to create a new lexical scope
function fn(obj){ with(obj){ a = 99 // When obj is b, a is added to the global object } } var o1 = {a:1} var o2 = {b:2} fn(o1); fn(o2); console.log(o1.a); console.log(o2.a); console.log(window.a)
c. Performance
The JavaScript engine will perform several performance optimizations in the compilation stage, some of which depend on the ability to conduct static analysis according to the lexical of the code and determine the location of all variables and functions in advance, so as to quickly find the identifier in the execution process. Found in Eval code (..) Or with, it can only simply assume that the pre judgment of the identifier position is invalid, and all optimizations may be meaningless. Therefore, the simplest way is not to do any optimization at all. They are not recommended.
4. Function scope and block level scope
a. Hide internal implementation
Follow the principle of minimum privilege and expose the necessary contents to a minimum. Part of the code is encapsulated in a function to form a function scope.
b. Execute function expression immediately
Immediately Invoked Function Expression (IIFE)
use:
var a = 10; (function IIFE(global){ var a = 20; console.log(a); // 20 console.log(global.a); // 10 })(window)
c. Block level scope
The variables declared by let and const will be implicitly bound to an existing scope, usually {..} Internal, block level scope also facilitates the recycling of variables that are no longer used.
use:
{ let a = 10; var b = 1; const c = 99; } // But when running here, both a and c will be recycled by gc console.log(b); console.log(a); // Will report an error console.log(c);
5. Declaration of promotion
For example: var a = 2; It will be treated as a single declaration of var a and a = 2. The first is the task in the compilation stage and the second is the task in the execution stage. Through precompiling the trilogy, the effect of variable promotion in js can be achieved:
1.establish GO/AO object 2.Find the formal parameter and variable declaration, and take the variable and formal parameter name as AO Property name with value undefined 3.Unify argument values and formal parameters 4.Find the function declaration in the function body, and assign the value to the function body
6. Scope closure
a. Understanding closures
Closure definition: when a function can remember and access its lexical scope, a closure is generated, even if the function is executed outside the current lexical scope.
Closure presentation:
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // This is the effect of closure
b. Closure and cycle
Problem code:
for (var i = 0; i < 6; i++) { setTimeout(function timer() { console.log(i); }, i * 1000); }
This code is expected to generate numbers 1 ~ 5, one per second, but the result is five 6. The source of this 6 is that after jumping out of the loop, i=6, because the timer callback function here shares the variable i of the same lexical scope, and setTimeout is a macro task, which is executed after the loop is completed, so all five 6's are printed;
Knowing the cause of the defect, we use IIFE to solve the defect, and use IIFE to create an independent lexical scope for each cycle, so as to achieve the desired effect
for (var i = 1; i < 6; i++) { (function IIEF(i) { setTimeout(function timer() { console.log(i); }, i * 1000); })(i) }
Let can also be used to declare variables, so that the variables declared by let can be attached to a block level scope every time, that is, {..} of each cycle It can also achieve the desired effect:
for (let i = 1; i < 6; i++) { setTimeout(function () { console.log(i); }, i * 1000); }
The let declaration of the for loop header also has a special behavior. During the loop, the variable will be declared in each iteration, and each subsequent iteration will initialize the variable with the value at the end of the last iteration.
c. Closure
Use the closure feature to function as a simple module manager:
var MyModules = (function Menager() { var modules = {}; function define(name, deps, impl) { for (var i = 0; i < deps.length; i++) { deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } function get(name) { return modules[name]; } return { get, define } })(); MyModules.define('bar',[],function(){ function hello(who){ return 'Let me introduce: ' + who; } return { hello } }); MyModules.define('foo',['bar'],function(bar){ //When there is only one element in the function, bar == [bar] var hungry = 'hippo'; function awesome(){ console.log(bar.hello(hungry).toUpperCase()); } return { awesome } }) var bar = MyModules.get('bar'); console.log(bar.hello('jiang')); var foo = MyModules.get('foo'); foo.awesome();
Future module mechanism, module mechanism of ES6:
// bar.js function hello(who){ return "Hello " + who; } export default hello; // foo.js import hello from './bar.js'; var hungry = 'hippo'; function awesome(){ console.log( hello(hungry).toUpperCase() ) } export default awesome; // baz.js import foo from './foo.js'; foo();
Use the node command to run Baz JS, you need a package Change the type configuration in JSON to "module" before it can run.
7. Others
a. Dynamic scope
The scope of JavaScritpt is lexical scope and static scope, which is determined when writing code (word segmentation / compilation stage), while the dynamic scope, this, is a good embodiment, which can be determined at runtime
b. Alternatives to block scope
Convert the code of ES6 into a new environment that can run before ES6:
// ES6 { let a = 2; console.log(a); } console.log(a); // Before ES6 try{throw 2}catch(a){ console.log(a); } console.log(a);
Why not use IIFE to create scopes here? First of all, the performance of try/catch is really poor (it has been improved). Second, IIFE is not completely equivalent to try/catch. If you take out a part of any piece of code and wrap it with a function, the meaning of the code will be changed. this, return and break/continue will all change. Therefore, IIFE is not a universal solution. It is only suitable for manual operation in some specific cases.
c. this binding in arrow function
Here is a demonstration of how many times a function object records itself being called
var obj = { count: 0, cool: function coolFn() { // setTimeout(() => { // this.count++; // console.log(this.count); // }, 1000); setTimeout((function timer() { console.log(this) this.count++; console.log(this.count); }).bind(this), 1000); } } obj.cool();
2, this
1.this
this is bound at runtime. Its binding depends on the way the function is called and has nothing to do with the function declaration.
There are four common ways to bind this:
Default binding:
var a =10; function foo(){ console.log(this.a); // this == .window } foo()
Implicit binding:
function foo(){ console.log(this.a); // obj here } var obj = { a: 2, foo: foo } obj.foo();
Display binding
Use call(..) apply(),bind(..) Function binding
function foo(){ console.log(this.a); // this here is obj } var obj = { a: 8, } foo.apply(obj);
new binding:
Use the new keyword to construct and call a function, and the following operations will be performed automatically:
1.Create a new object on the first flight of the function, such as var this = {}; 2.The team will be executed[[prototype]]connect 3.This new object will be bound to the function call this 4.If by new If the called function does not show that it returns other objects, a new object will be returned automatically
Used in Code:
function foo(){ this.a = 10; console.log(this.a); // this here is obj } var obj = new foo();
2. Ignored this
When null or undefined is passed into call, apply and bind as the binding object of this, these values will be ignored during the call. The actual operation uses the default binding rules:
function foo(){ console.log(this.a); } var a = 2; foo.call(null);
Use apply(..) To expand the array, bind(..) Function can be coriolised (set some parameters in advance):
function foo(a,b){ // Expand array console.log('a:' + a, 'b:' + b); } foo.apply(null,[2,3]); var bar =foo.bind(null,2); //Function coritization bar(3);
Always using null to ignore this binding may produce some side effects. When this is really used in the function, this will be bound to the window global object, and it is possible to modify the global object. Of course, this is not good. It would be safer to create an empty non delegate object instead of null: VAR DMZ = object create(null);
For example:
var DMZ = Object.create(null); function foo(a,b){ // Expand array this.a = 10; console.log('a:' + a, 'b:' + b); } foo.apply(DMZ,[2,3]);
3. Soft binding
Assigning a value other than global object and undefined to the default binding can not only achieve the same effect as hard binding, but also retain the ability of implicit binding or explicit binding to modify this.
if (!Function.prototype.softBind) { Function.prototype.softBind = function (obj) { var fn = this; var curried = [].splice.call(arguments, 1); var bound = function () { return fn.apply( (!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments) ) } bound.prototype = Object.create(fn.prototype); return bound; } } function foo() { console.log("name:" + this.name); } var obj = { name: 'obj' }; var obj2 = { name: 'obj2' }; obj2.bar = foo.softBind(obj); obj2.bar(); setTimeout(obj2.bar, 1000);
4. Arrow function
When this binding of the arrow function, the position of the arrow function is determined according to the outer (function or global) scope, and can no longer be modified in any way.
Look at a listing Code:
function foo(){ return (a)=>{ console.log(this.a); } } var obj1 = { a:2 } var obj2 = { a:3 } var bar = foo.call(obj1); // It has been determined that this of the arrow function points to obj1 and can no longer be changed bar.call(obj2); // 2 instead of 3
Before es6, the mode similar to domain arrow function was used:
function foo() { var self = this; setTimeout(function () { console.log(self.name); }, 1000); } var obj = { name: 'obj', } foo.call(obj);
3, Object
1. Data type
Basic data types in JS: string, number, Boolean, object, null, undefined. JS built-in objects (built-in functions, which can be new): string, number, Boolean, object, Function, Array, Date, RegExp, Error
use:
var strPrimitive = "I am string"; typeof strPrimitive; // "string" strPrimitive instanceof String; // false var strObject = new String("I am String"); typeof strObject; // "object" strObject instanceof String; // true
Type conversion: when a literal property or method is used, the corresponding built-in constructor will be used to wrap it into an object and call the property or function: for example
console.log('I am string'.length); // ==> new String('I am string').length; console.log(42.324.toFixed(2)); // ==> new Number(42.324).toFixed()
The difference is that null and undefined have no corresponding construction form, and they are followed by text form. On the contrary, Date has only structure and no literal form. For Object, Array, Function and RegExp, they are objects in both literal form and constructor. Error objects are usually created automatically when an error occurs. You can also use new Error(..) Manually created. throw new Error(..) is thrown with the throw keyword when used
2. Attribute descriptor
Attribute descriptors include "data descriptor" and "access descriptor".
Attribute descriptor:
let obj = Object.create(null); Object.defineProperty(obj, 'name', { value: 'Zhang San', // Attribute value writable: false, // Property modifiable configurable: false, // Whether the attribute can be deleted and whether the attribute descriptor can be modified enumerable: true // Whether the property is enumerable });
Accessor descriptor (defined in two ways):
var obj = { get name() { return _name_; }, set name(value) { _name_ = value; } } obj.name = 'jiang'; Object.defineProperty(obj,'age',{ get : function(){ return _age_; }, set : function(value){ _age_ = value; } }) obj.age = 18; console.log(obj);
3. Traversal
Here is the for Of traversal, for The of loop will first request an iterator object @ @ iterator from the accessed object, and then traverse all the return values through the next() method of the iterator object:
var myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var it = myArray[Symbol.iterator](); while (true) { var result = it.next(); if (result.done) break; console.log(result.value); }
However, ordinary objects do not have built-in @ @ iterators. When you want to do such a circular operation, you can also define a @ @ iterator object for the object you want to traverse, as follows:
var myObject = { name: "John", age: 34, isMarried: false }; Object.defineProperty(myObject, Symbol.iterator, { enumerable: false, writable: false, configurable: true, value: function () { var o = this; var idx = 0; var ks = Object.keys(o); return { next() { return { value: o[ks[idx++]], done: idx > ks.length } } } } }) for (let key of myObject) { console.log(key); }
Use for Every time the of loop calls the next() method of the iterator object, the internal pointer will move forward and return the next value of the object attribute list. Use this feature to make an iterator that will never end (such as returning random numbers, incremental values, unique identifiers, etc.):
var randoms = { [Symbol.iterator]:function(){ return { next(){ return { value:Math.random(), done:false } } } } } var random_pool = []; for(let n of randoms){ random_pool.push((n*100).toFixed(2)); if(random_pool.length>=100){ break; } } console.log(random_pool);
4, Prototype
1. Shielding and attribute setting
In JS, when MyObject Foo = "bar" there are three situations that occur during the assignment operation:
1.If in[[Prototype]]The upper layer of the chain is named foo Normal data access property of, which is non read-only(writalbe)When, it will be directly in myObject Add a file named foo A new attribute of, which is a mask attribute. 2.If in[[]Prototype]The upper layer of the chain exists foo,But it is marked as read-only(writable)The existing properties cannot be modified or myObject Create mask properties on. In non strict mode, the assignment statement will be ignored and an error will be thrown in strict mode. Of course, this is mainly to simulate the inheritance of class attributes. 3.If in[[Prototype]]The upper layer of the chain exists foo And it's a setter,Then one will call this setter,foo Not added to(Or shielded from)muObject,It will not be redefined foo this setter
In the second and third cases, foo can also be masked, but the = operator cannot be used to assign values, but object defineProperty(..) To add foo to myObject.
2. Succession
a.instanceof function
Use instanceof to judge a's "ancestor" (delegate Association) from the perspective of "class":
function Foo() { }; var a = new Foo(); console.log(a instanceof Foo); // Using the bind function here will not have any impact. The binding priority of new to this is higher than that of bind function var l = { a: 10 }; function Foo() { } var a = new (Foo.bind(l)); console.log(a) console.log(a instanceof Foo);
instanceof answered here: is there a point to foo in the whole [[prototype]] chain of A Prototype object?
b.isPrototypeOf function
Use isPrototypeOf to directly judge whether an object is on the prototype chain of another object:
var myObject = {}; var anotherObject = {}; Object.setPrototypeOf(myObject, anotherObject); console.log(anotherObject.isPrototypeOf(myObject));
c.__proto__ realization
In most browsers, it also supports a non-standard method to access the internal [[prototype]] attribute, which has the same function as object getPrototypeOf(..), It is__ proto__ Property, which cannot exist in the object you are using, but is similar to its common functions (. toString(..) isPrototypeOf(..), And so on), which exists in the built-in object In prototype__ proto__ The implementation of is as follows:
Object.defineProperty(Object.prototype, "_proto_", { get: function () { return Object.getPrototypeOf(this); }, set: function (value) { Object.setPrototypeOf(this, value); } }) console.log({}._proto_);
d.create use
Object.create(..) It is a new function in ES5. To use it in the environment before ES5, you need a simple polyfill code, which partially implements object create(..) Functions of:
Object.create = function(obj){ function F(){} F.prototype = obj; return new F(); }
Here, the polyfill function, when null is passed in, cannot create a "relatively empty to empty object", and the real object create(..) The second parameter of the function can define the attributes and attribute descriptors contained in the new object. use:
console.log(Object.create(null)); // It is suitable for loading data and shielding the interference of prototype chain var anotherObject = {}; var myObject = Object.create(anotherObject, { "name": { value: "Nicholas", writable: true, enumerable: true, configurable: true }, "age": { value: 29, writable: true, enumerable: true, configurable: true } }); console.log(myObject);
e. Behavior entrustment
Requirements: the parent class of general control behavior (such as Widget) and the special control subclass (such as Button) that inherits the parent class
Implement class style code in JS:
control"class" // Parent class function Widget(width,height){ this.with = width || 50; this.height = height || 50; this.$elem = null; } Widget.prototype.render = function($where){ if(this.$elem){ this.$elem.css({ width:this.width + "px", height:this.height + "px" }).appendTo($where); } }; // Subclass function Button(width,height,label){ Widget.call(this,width,height); this.label = label || "Default"; this.$elem = $('<button>').text(this.label); } // Let Button inherit from Widget Button.prototype = Object.create(Widget.prototype); Button.prototype.constructor = Button; // Override render method Button.prototype.render = function($where){ Widget.prototype.render.call(this,$where); this.$elem.click(this.onClick.bind(this)); } Button.prototype.onClick = function(){ console.log("Button '" + this.label + "' clicked!"); } $(document).ready(function(){ var $body = $(document.body); var btn1 = new Button(125,30,"Hello"); var btn2 = new Button(150,40,"World"); btn1.render($body); btn2.render($body); })
Use object association style delegation to implement bright / button:
// Delegate control object var Widget = { init: function (width, height) { this.width = width || 50; this.height = height || 50; this.$elem = null; }, insert: function ($where) { if (this.$elem) { this.$elem.css({ width: this.width + "px", height: this.height + "px" }).appendTo($where); } } } var Button = Object.create(Widget); Button.setup = function (width, height, label) { // Delegate call this.init(width, height); this.label = label || "Default"; this.$elem = $('<button>').text(this.label); } Button.build = function ($where) { // Delegate call this.insert($where); this.$elem.click(this.onClick.bind(this)); } Button.onClick = function () { console.log("Button '" + this.label + "' clicked!"); } $(document).ready(function(){ var $body = $(document.body); var btn1 = Object.create(Button); btn1.setup(125,30,"Hello"); btn1.build($body); var btn2 = Object.create(Button); btn2.setup(150,40,"World"); btn2.build($body); })