Easily ignored points of js Foundation

Keywords: Javascript JSON Attribute Java

1

if ( typeof v === 'undefined') {  // true
}
if ( typeof null === 'object') { // true
}

2

Number(null)  //0
5 + null  //5

Number(undefined)  //NaN
5  + undefined  //NaN

3 integers and floating points

There are no integers at all at the bottom of JavaScript language. All numbers are decimals (64 bit floating-point numbers). It is easy to be confused that some operations can only be completed by integers. At this time, JavaScript will automatically convert 64 bit floating-point numbers to 32-bit integers, and then perform operations. Please refer to the "bit operations" section of the chapter "operators".

Since floating-point numbers are not exact values, special care should be taken when comparing and calculating decimals.

1 === 1.0 // true

0.1 + 0.2 === 0.3 // false

0.3 / 0.1
// 2.9999999999999996

(0.3 - 0.2) === (0.2 - 0.1)
// false

4

The precision can only reach 53 binary bits at most, which means that integers with absolute value less than 2 to the power of 53, i.e. - 253 to 253, can be expressed accurately.

Math.pow(2, 53)
// 9007199254740992

Math.pow(2, 53) + 1
// 9007199254740992

Math.pow(2, 53) + 2
// 9007199254740994

Math.pow(2, 53) + 3
// 9007199254740996

Math.pow(2, 53) + 4
// 900719925474099


Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324

5 base of value

When literal is used to express a value directly, JavaScript provides four decimal representation methods for integer: decimal, hexadecimal, octal and binary.

Decimal: number without leading 0.
Octal: a numeric value with a prefix of 0o or 0o, or a leading 0 and only eight Arabic numbers of 0-7.
Hexadecimal: a numeric value with a prefix of 0x or 0x.
Binary: value with prefix 0b or 0b.

6 positive and negative zero

-0 === +0 // true
0 === -0 // true
0 === +0 // true

+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'

(1 / +0) === (1 / -0) // false

7 NaN not a number

typeof NaN // 'number'

NaN === NaN // false

[NaN].indexOf(NaN) // -1

Boolean(NaN) // false

NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN

// Scene 1
Math.pow(2, 1024)
// Infinity

// Scene two
0 / 0 // NaN
1 / 0 // Infinity

Infinity === -Infinity // false

1 / -0 // -Infinity
-1 / -0 // Infinity

Infinity > 1000 // true
-Infinity < -1000 // true

Infinity > NaN // false
-Infinity > NaN // false

Infinity < NaN // false
-Infinity < NaN // false

7parseInt()

If the parameter of parseInt is not a string, it will be converted to a string and then converted.

parseInt(1.23) // 1
// Equate to
parseInt('1.23') // 1

When a string is converted to an integer, it is converted one by one. If a character cannot be converted to a number, it will not go on and return the converted part.

parseInt('8a') //8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15

parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') // NaN
parseInt('+1') // 1

Therefore, the return value of parseInt can only be a decimal integer or NaN.
If the string starts with either 0x or 0x, parseInt parses it as a hexadecimal number.

paeseInt('0x10') //16

If the string starts with 0, it is parsed in decimal.

parseInt('011') //11

For numbers that automatically turn to scientific notation, parseInt treats the representation of scientific notation as a string, leading to some strange results.

parseInt(1000000000000000000000.5) // 1
// Equate to
parseInt('1e+21') // 1

parseInt(0.0000008) // 8
// Equate to
parseInt('8e-7') // 8

The parseInt method can also accept a second parameter (between 2 and 36) that represents the base of the parsed value and returns the corresponding decimal number of the value. By default, the second parameter of parseInt is 10, which means the default is decimal to decimal.

parseInt('1000') // 1000
// Equate to
parseInt('1000', 10) // 1000

parseInt('1000', 2) // 8
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512

If the second parameter is not a numeric value, it is automatically converted to an integer. This integer can get meaningful results only when it is between 2 and 36. If it is out of this range, NaN will be returned. If the second parameter is 0, undefined, and null, it is ignored directly.

parseInt('10', 37) // NaN
parseInt('10', 1) // NaN
parseInt('10', 0) // 10
parseInt('10', null) // 10
parseInt('10', undefined) // 10

If the string contains characters that are not meaningful for the specified base, only values that can be converted are returned starting from the highest order. If the highest order cannot be converted, NaN is returned directly.

parseInt('1546', 2) // 1
parseInt('546', 2) // NaN

As mentioned earlier, if the first parameter of parseInt is not a string, it will be converted to a string first. This will lead to some unexpected results.

parseInt(0x11, 36) // 43
parseInt(0x11, 2) // 1

// Equate to
parseInt(String(0x11), 36)
parseInt(String(0x11), 2)

// Equate to
parseInt('17', 36)
parseInt('17', 2)

In the above code, the hexadecimal 0x11 will be converted to the decimal 17 and then to the string. Then, the string 17 is interpreted in 36 base or binary, and the results 43 and 1 are returned.

In this way, we need to pay special attention to the octal prefix 0.

parseInt(011, 2) // NaN

// Equate to
parseInt(String(011), 2)

// Equate to
parseInt(String(9), 2)

In the above code, 011 in the first line will be converted to string 9 first, because 9 is not a valid binary character, so NaN is returned. If parseInt('011 ', 2) is calculated directly, 011 will be treated as binary and return 3.

JavaScript no longer allows numbers with a prefix of 0 to be treated as octal numbers, but requires that this 0 be ignored. However, in order to ensure compatibility, most browsers do not deploy this provision.

8parseFloat

parseFloat(true)  // NaN
Number(true) // 1

parseFloat(null) // NaN
Number(null) // 0

parseFloat('') // NaN
Number('') // 0

parseFloat('123.45#') // 123.45
Number('123.45#') // NaN

9 isNaN

However, isNaN returns false for an empty array and an array with only one numeric member.

isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false

The reason why the above code returns false is that these arrays can be converted into values by the Number function. Please refer to the chapter "data type conversion".

Therefore, it is best to judge the data type before using isNaN.

function myIsNaN(value) {
  return typeof value === 'number' && isNaN(value);
}

The more reliable way to judge NaN is to use the characteristic that NaN is the only value not equal to its own.

function myIsNaN(value) {
  return value !== value;
}

10 identifier

These are legal identifiers.

arg0
_tmp
$elem
π
These are illegal identifiers.

1a / / the first character cannot be a number
 23 / / ditto
 ***/ / identifiers cannot contain asterisks
 a+b / / identifier cannot contain a plus sign
 -d / / identifiers cannot contain minus signs or hyphens
 Chinese is a legal identifier and can be used as a variable name.

var temporary variable = 1;
JavaScript has reserved words that cannot be used as identifiers: arguments, break, case, catch, class, const, continue, debugger, default, delete, do, else, enum, eval, export, extends, false, finally, for, function, if, implements, import, in, instanceof, interface, let, new, null, package, private, protected, public, retu rn,static,super,switch,this,throw,true,try,typeof,var,void,while,with,yield. 

11 string

If a long string has to be split into multiple lines, you can use a backslash at the end of each line.

var longString = 'Long \
long \
long \
string';

longString
// "Long long long string"

The above code indicates that after the backslash is added, the string originally written in one line can be written in multiple lines. However, the output is still a single line, the effect is exactly the same as writing on the same line. Note that the backslash must be followed by a newline character and no other characters (such as spaces) or an error will be reported.
The join operator (+) can connect multiple single line strings, split the long string into multiple lines to write, and the output is also a single line

var longString = 'Long '
  + 'long '
  + 'long '
  + 'string';

If you want to output multiline strings, there is an alternative to using multiline comments.

(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"

The backslash (\) has a special meaning in the string, which is used to represent some special characters, so it is also called escape character.

Special characters that need to be escaped with a backslash are mainly as follows.

\0 : null(\u0000)
\b: Back key (\ u0008)
\f: Page break (\ u000C)
\n: Line break (\ u000A)
\r: Enter (\ u000D)
\t: Tab (\ u0009)
\v: Vertical tab (\ u000B)
\': single quotation mark (\ u0027)
\": double quotes (\ u0022)
\: backslash (\ u005C)

A string can be treated as an array of characters, so you can use the array's square bracket operator to return the character of a position (the position number starts at 0).

var s = 'hello';
s[0] // "h"
s[1] // "e"
s[4] // "o"

// Use bracket operators directly on strings
'hello'[1] // "e"

However, the similarity between strings and arrays is nothing more than that. In fact, you cannot change a single character in a string.

var s = 'hello';

delete s[0];
s // "hello"

s[1] = 'a';
s // "hello"

s[5] = '!';
s // "hello"

12 Base64 transcoding

JavaScript natively provides two Base64 related methods.
btoa(): any value converted to Base64 encoding
atob(): Base64 encoding changed to the original value

var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"

Note that these two methods are not suitable for non ASCII characters and error will be reported.

btoa('Hello') // Report errors

To convert non ASCII characters to Base64 encoding, you must insert a transcoding link in the middle, and then use these two methods.

function b64Encode(str) {
  return btoa(encodeURIComponent(str));
}

function b64Decode(str) {
  return decodeURIComponent(atob(str));
}

b64Encode('Hello') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "Hello"

13 object

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}

For... The in loop has two points for attention.

It traverses all the enumerable properties of the object, and skips the non ergodic properties.
It not only traverses the properties of the object itself, but also the inherited properties.
For example, objects inherit the toString property, but for The in loop does not traverse this property.

var person = { name: 'Lao Zhang' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
// name

14 function

In the second line of the above code, when calling F, f is only declared and has not been assigned, which equals undefined, so an error will be reported. Therefore, if the function command and assignment statement are used to declare the same function at the same time, the definition of assignment statement is always used in the end.

var f = function () {
  console.log('1');
}

function f() {
  console.log('2');
}

f() // 1

The toString method of the function returns a string containing the source code of the function.

function f() {
  a();
  b();
  c();
}

f.toString()
// function f() {
//  a();
//  b();
//  c();
// }

For the top-level function, the variables declared outside the function are global variable s, which can be read inside the function.

var v = 1;

function f() {
  console.log(v);
}

f()
// 1

The above code shows that the global variable v can be read inside the function f.

A variable defined inside a function that cannot be read outside is called a local variable.

function f(){
  var v = 1;
}

v // ReferenceError: v is not defined

In the above code, the variable v is defined inside the function, so it is a local variable and cannot be read outside the function.

The variables defined within the function will overwrite the global variables with the same name in the scope.

var v = 1;

function f(){
  var v = 2;
  console.log(v);
}

f() // 2
v // 1

In the above code, the variable V is defined both outside and inside the function. As a result, the local variable V covers the global variable v.

Note that for the var command, local variables can only be declared inside the function, but in other blocks, they are all global variables.
Declared in other blocks, they are all global variables.
Declared in other blocks, they are all global variables.

if (true) {
  var x = 5;
}
console.log(x);  // 5

In the above code, variable x is declared in the conditional judgment block, and the result is a global variable, which can be read outside the block.

The function itself is also a value and has its own scope. Its scope is the same as that of a variable, which is the scope of its declaration, regardless of the scope of its runtime.

arguments
It should be noted that although arguments are very similar to arrays, they are an object. Array specific methods, such as slice and forEach, cannot be used directly on arguments objects.

If you want the arguments object to use the array method, the real solution is to turn the arguments into a real array. Here are two common conversion methods: slice method and fill in the new array one by one.

var args = Array.prototype.slice.call(arguments);

// perhaps
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}

closure
There are two main uses of closures: one is to read the variables inside the function, and the other is to keep these variables in memory all the time, that is, closures can make its birth environment exist all the time. Let's see the following example. Closures make internal variables remember the results of the last call.

function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

In the above code, start is the internal variable of the function createIncrementor. Through the closure, the state of start is preserved, and each call is calculated on the basis of the previous call. As you can see from it, the closure inc makes the internal environment of function createIncrementor exist all the time. Therefore, a closure can be seen as an interface to the internal scope of a function.

Why is that? The reason is that inc is always in memory, and the existence of inc depends on createIncrementor, so it is always in memory and will not be recycled by the garbage collection mechanism after the call.

Another use of closures is to encapsulate private properties and methods of objects.

Function expression called immediately (IIFE)
Sometimes, we need to call the function as soon as we have defined it. At this point, you cannot add parentheses after the definition of the function, which will cause syntax errors.

function(){ /* code */ }();
// SyntaxError: Unexpected token (

The reason for this error is that the function keyword can be used as either a statement or an expression.

// Sentence
function f() {}

// Expression
var f = function f() {}

In order to avoid ambiguity in parsing, the JavaScript engine stipulates that if the function keyword appears at the beginning of the line, all idioms will be interpreted. Therefore, after the JavaScript engine sees the function keyword at the beginning of the line, it thinks that this paragraph is the definition of the function and should not end with parentheses, so it reports an error.

The solution is not to let function appear at the beginning of the line, let the engine understand it as an expression. The simplest way to do this is to put it in parentheses.

(function(){ /* code */ }());
// perhaps
(function(){ /* code */ })();

Both of the above methods start with parentheses, and the engine will think that it is followed by a representation rather than a function definition statement, so errors are avoided. This is called "immediately invoked function expression" (IIFE for short).

Note that the last semicolon in both of the above is required. If the semicolon is omitted and two iifes are connected, an error may be reported.

// Report errors
(function(){ /* code */ }())
(function(){ /* code */ }())

There is no semicolon between the two lines of the above code, JavaScript will interpret them together, and the second line will be interpreted as the parameters of the first line.

By extension, any method that lets an interpreter handle function definitions in terms of expressions can produce the same effect, such as the following three ways of writing.

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

Even writing like this is OK.

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

In general, this "immediately executed function expression" is used only for anonymous functions. It has two purposes: first, it does not need to name the function to avoid polluting the global variables; second, it forms a separate scope inside IIFE, which can encapsulate some private variables that cannot be read outside.

// Write a way
var tmp = newData;
processData(tmp);
storeData(tmp);

// Writing two
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

In the above code, method 2 is better than method 1 because it completely avoids polluting global variables.

15 array

// Setting negative values
[].length = -1
// RangeError: Invalid array length

// The number of array elements is greater than or equal to the 32 power of 2
[].length = Math.pow(2, 32)
// RangeError: Invalid array length

// Set string
[].length = 'abc'
// RangeError: Invalid array length

It is worth noting that since an array is essentially an object, you can add attributes to the array, but this does not affect the value of the length attribute.

var a = [];

a['p'] = 'abc';
a.length // 0

a[2.1] = 'abc';
a.length // 0

The above code sets the key of the array to string and decimal respectively, and the result does not affect the length property. Because the value of the length property is equal to the maximum number key plus 1, and there is no integer key in this array, the length property remains at 0.

If the key name of an array is to add a value that is out of range, the key name is automatically converted to a string.

var arr = [];
arr[-1] = 'a';
arr[Math.pow(2, 32)] = 'b';

arr.length // 0
arr[-1] // "a"
arr[4294967296] // "b"

In the above code, we added two illegal numeric keys to the array arr, and the length property did not change. These numeric keys become string key names. The reason why the last two lines get the value is that when you get the key value, the number key name will be converted to a string by default.

The slice method of an array can turn an "array like object" into a real array.

var arr = Array.prototype.slice.call(arrayLike);

In addition to turning into a real array, there is another way to use array like objects, that is, to put array methods on objects through call().

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);

In the above code, arrayLike represents an array like object. It is not allowed to use the forEach() method of the array, but it can be called by grafting forEach() onto arrayLike through call().

The following example uses this method to call the forEach method on the arguments object.

// forEach method
function logArgs() {
  Array.prototype.forEach.call(arguments, function (elem, i) {
    console.log(i + '. ' + elem);
  });
}

// Equivalent to for loop
function logArgs() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(i + '. ' + arguments[i]);
  }
}

Strings are also array like objects, so you can also traverse them with Array.prototype.forEach.call.

Array.prototype.forEach.call('abc', function (chr) {
  console.log(chr);
});
// a
// b
// c

Note that this method is slower than using the array native forEach directly, so it's better to first convert "array like objects" to real arrays, and then directly call the array forEach method.

var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
  console.log(chr);
});
// a
// b
// c

16 comparison

NaN === NaN  // false
+0 === -0 // true

undefined === undefined // true
null === null // true

When comparing data of two composite types (object, array, function), it is not to compare whether their values are equal, but whether they point to the same address.

{} === {} // false
[] === [] // false
(function () {} === function () {}) // false

If two variables refer to the same object, they are equal.

var v1 = {};
var v2 = v1;
v1 === v2 // true
1 == 1.0
 / / equal to
1 === 1.0

In the above code, the array [1] will be converted to a numerical value before comparison; when compared with a string, it will be converted to a string before comparison; when compared with a Boolean value, both the object and the Boolean value will be converted to a numerical value before comparison.

17 void

Look at the code below.

<script>
function f() {
  console.log('Hello World');
}
</script>
<a href="http://Example. Com "onclick =" f(); return false; "> Click</a>

In the above code, after clicking the link, the onclick code will be executed first. Since onclick returns false, the browser will not jump to example.com.

The void operator can replace the above.

< a href = "javascript: void (f())" > text</a>
Here's a more practical example, where users click on a link to submit a form without generating a page Jump.

<a href="javascript: void(document.form.submit())">
  Submission
</a>

18 conversion of data types

// Value: converted to original value
Number(324) // 324

// String: if it can be resolved to a value, it will be converted to the corresponding value
Number('324') // 324

// String: returns NaN if it cannot be parsed to a value
Number('324abc') // NaN

// Empty string to 0
Number('') // 0

// Boolean value: true to 1, false to 0
Number(true) // 1
Number(false) // 0

// undefined: convert to NaN
Number(undefined) // NaN

// null: convert to 0
Number(null) // 0

19 try...catch...finally

function f() {
  try {
    console.log(0);
    throw 'bug';
  } catch(e) {
    console.log(1);
    return true; // This sentence would have been delayed until the end of the finally block
    console.log(2); // Will not run
  } finally {
    console.log(3);
    return false; // This sentence will cover the previous return
    console.log(4); // Will not run
  }

  console.log(5); // Will not run
}

var result = f();
// 0
// 1
// 3

result
// false

20 Object

var obj = Object(1);
obj instanceof Object // true
obj instanceof Number // true

var obj = Object('foo');
obj instanceof Object // true
obj instanceof String // true

var obj = Object(true);
obj instanceof Object // true
obj instanceof Boolean // true

In the above code, the parameters of the Object function are values of various original types, and the converted Object is the wrapper Object corresponding to the original type value.

If the parameter of the Object method is an Object, it always returns the Object, that is, without conversion.

var arr = [];
var obj = Object(arr); // Returns the original array
obj === arr // true

var value = {};
var obj = Object(value) // Return to original object
obj === value // true

var fn = function () {};
var obj = Object(fn); // Return to original function
obj === fn // true

With this, you can write a function that determines whether a variable is an object.

function isObject(value) {
  return value === Object(value);
}

isObject([]) // true
isObject(true) // false

For general objects, Object.keys() and Object.getOwnPropertyNames() return the same results. Only when non enumerable properties are involved, will there be different results. The Object.keys method only returns enumerable properties (see the description of object properties chapter for details). The Object.getOwnPropertyNames method also returns enumerable property names.

var a = ['Hello', 'World'];

Object.keys(a) // ["0", "1"]
Object.getOwnPropertyNames(a) // ["0", "1", "length"]

In the above code, the length property of the array is an enumerable property, so it only appears in the return result of the Object.getOwnPropertyNames method.

Because JavaScript does not provide a method to calculate the number of object properties, these two methods can be used instead.

var obj = {
  p1: 123,
  p2: 456
};

Object.keys(obj).length // 2
Object.getOwnPropertyNames(obj).length // 2

In general, the Object.keys method is almost always used to traverse the properties of an object.

That is to say, Object.prototype.toString can see exactly what type a value is.

Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"

With this feature, you can write a more accurate type judgment function than the typeof operator.

var type = function (o){
  var s = Object.prototype.toString.call(o);
  return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};

type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"

On the basis of the above type function, you can also add a special method to judge a certain type of data.

var type = function (o){
  var s = Object.prototype.toString.call(o);
  return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};

['Null',
 'Undefined',
 'Object',
 'Array',
 'String',
 'Number',
 'Boolean',
 'Function',
 'RegExp'
].forEach(function (t) {
  type['is' + t] = function (o) {
    return type(o) === t.toLowerCase();
  };
});

type.isObject({}) // true
type.isNumber(NaN) // true
type.isRegExp(/abc/) // true

The Object.defineProperty() method allows you to describe an object through a property, define or modify a property, and then return the modified object. Its usage is as follows.

Object.defineProperty(object, propertyName, attributesObject)

The Object.defineProperty method takes three parameters, one after the other.

Object: the object of the property
propertyName: string indicating the property name
attributesObject: attribute description object
For example, obj.p can be defined as follows.

var obj = Object.defineProperty({}, 'p', {
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false
});

obj.p // 123

obj.p = 246;
obj.p // 123

If you define or modify multiple properties at once, you can use the Object.defineProperties() method.

var obj = Object.defineProperties({}, {
  p1: { value: 123, enumerable: true },
  p2: { value: 'abc', enumerable: true },
  p3: { get: function () { return this.p1 + this.p2 },
    enumerable:true,
    configurable:true
  }
});

obj.p1 // 123
obj.p2 // "abc"
obj.p3 // "123abc"

Copy of object
Sometimes, we need to copy all the properties of one object to another, which can be implemented in the following way.

var extend = function (to, from) {
  for (var property in from) {
    to[property] = from[property];
  }

  return to;
}

extend({}, {
  a: 1
})
// {a: 1}

The problem with the above method is that if an accessor defined property is encountered, only the value will be copied.

extend({}, {
  get a() { return 1 }
})
// {a: 1}

To solve this problem, we can copy properties through the Object.defineProperty method.

var extend = function (to, from) {
  for (var property in from) {
    if (!from.hasOwnProperty(property)) continue;
    Object.defineProperty(
      to,
      property,
      Object.getOwnPropertyDescriptor(from, property)
    );
  }

  return to;
}

extend({}, { get a(){ return 1 } })
// { get a(){ return 1 } })

In the above code, the line of hasOwnProperty is used to filter out inherited properties. Otherwise, an error may be reported because the Object.getOwnPropertyDescriptor cannot read the property description object of the inherited property.

Sometimes it is necessary to freeze the read-write state of the object to prevent the object from being changed. JavaScript provides three methods of freezing, the weakest is Object.preventExtensions, the second is Object.seal, and the strongest is object.free.

21 Array

The Array.isArray method returns a Boolean value indicating whether the parameter is an array. It can make up for the shortcomings of the typeof operator.

var arr = [1, 2, 3];

typeof arr // "object"
Array.isArray(arr) // true

The valueOf method is a method owned by all objects and represents the evaluation of that object. The valueOf methods of different objects are different. The valueOf method of array returns the array itself.

var arr = [1, 2, 3];
arr.valueOf() // [1, 2, 3]

toString method is also a general method of objects. toString method of array returns string form of array.

var arr = [1, 2, 3];
arr.toString() // "1,2,3"

var arr = [1, 2, 3, [4, 5, 6]];
arr.toString() // "1,2,3,4,5,6"

The push method is used to add one or more elements at the end of an array and return the length of the array after adding new elements. Note that this method changes the original array.
The pop method removes the last element of the array and returns it. Note that this method changes the original array.
The shift() method is used to delete the first element of the array and return it. Note that this method changes the original array.
The unshift() method is used to add elements at the first position of the array and return the length of the array after adding new elements. Note that this method changes the original array.

When push and pop are used together, a stack structure of "last in, first out" is formed.
push() and shift() are used together to form a "first in, first out" queue structure.

var arr = [];
arr.push(1, 2);
arr.push(3);
arr.pop();
arr // [1, 2]

var a  = ['1','2]
a.unshift('3')
a // [3,1,2]

The join() method uses the specified parameter as the separator to connect all array members as a string. If no parameters are provided, they are separated by commas by default.

var a = [1, 2, 3, 4];

a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"

If the array member is undefined or null or empty, it is converted to an empty string.

[undefined, null].join('#')
// '#'

['a',, 'b'].join('-')
// 'a--b'

With the call method, this method can also be used for strings or array like objects.

Array.prototype.join.call('hello', '-')
// "h-e-l-l-o"

var obj = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(obj, '-')
// 'a-b'

concat
If the array members include objects, the concat method returns a shallow copy of the current array. The so-called "shallow copy" refers to that the new array copies the reference of the object.

var obj = { a: 1 };
var oldArray = [obj];

var newArray = oldArray.concat();

obj.a = 2;
newArray[0].a // 2

slice method is used to extract a part of the target array and return a new array. The original array remains unchanged.

arr.slice(start, end)
;
var a = ['a', 'b', 'c'];

a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"]

Its first parameter is the start position (starting from 0), and its second parameter is the end position (but the element of the position itself is not included). If the second parameter is omitted, it is returned to the last member of the original array.

An important application of slice method is to transform objects similar to arrays into real arrays.

Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
// ['a', 'b']

Array.prototype.slice.call(document.querySelectorAll("div"));
Array.prototype.slice.call(arguments);

The splice method is used to delete some members of the original array and add new array members at the deleted location. The return value is the deleted element. Note that this method changes the original array.

arr.splice(start, count, addElement1, addElement2, ...);

The first parameter of splice is the starting position of deletion (starting from 0), and the second parameter is the number of elements deleted. If there are more parameters later, it means that these are the new elements to be inserted into the array.

If you just insert the element, the second parameter of the splice method can be set to 0.

var a = [1, 1, 1];

a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]

Delete and insert data

var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]

The sort method sorts the array members, by default, in dictionary order. After sorting, the original array will be changed.

['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']

[4, 3, 2, 1].sort()
// [1, 2, 3, 4]

[11, 101].sort()
// [101, 11]

[10111, 1101, 111].sort()
// [10111, 1101, 111]

The last two examples of the above code need special attention. The sort() method is not sorted by size, but by dictionary order. In other words, the values are converted into strings and then compared in dictionary order, so 101 is ahead of 11.

If you want the sort method to sort in a custom way, you can pass in a function as an argument.

[10111, 1101, 111].sort(function (a, b) {
  return a - b;
})
// [111, 1101, 10111]

The map method passes all the members of the array to the parameter function in turn, and then forms a new array to return each execution result

[1, 2, 3].map(function(elem, index, arr) {
  return elem * index;
});
// [0, 2, 6]

The map method can also accept a second parameter, which is used to bind the this variable inside the callback function

var arr = ['a', 'b', 'c'];

[1, 2].map(function (e) {
  return this[e];
}, arr)
// ['b', 'c']

The forEach method is similar to the map method in that it performs parameter functions on all members of the array in turn. However, the forEach method does not return a value and is only used to manipulate the data. That is, if the purpose of array traversal is to get the return value, use the map method, otherwise use the forEach method.

The forEach method can also accept the second parameter, which binds the this variable of the parameter function.

var out = [];

[1, 2, 3].forEach(function(elem) {
  this.push(elem * elem);
}, out);

out // [1, 4, 9]

Note that the forEach method cannot interrupt execution and will always traverse all members. If you want to meet some conditions, break the traversal and use the for loop.

var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) {
  if (arr[i] === 2) break;
  console.log(arr[i]);
}
// 1

The filter method is used to filter the array members, and the members satisfying the conditions form a new array to return.
The reduce method and the reduceRight method process each member of the array in turn, and finally accumulate to a value. The difference between them is that reduce is processed from left to right (from the first member to the last member), while reduceRight is processed from right to left (from the last member to the first member), and the others are exactly the same.

[1, 2, 3, 4, 5].reduce(function (a, b) {
  console.log(a, b);
  return a + b;
})
// 1 2
// 3 3
// 6 4
// 10 5
//Final result: 15

The first parameter of both the reduce method and the reduceRight method is a function. The function takes the following four parameters.

Cumulative variable, default to the first member of the array
Current variable, defaults to the second member of the array
Current location (from 0)
Original array
Of the four parameters, only the first two are required, and the last two are optional.

If you want to specify an initial value for a cumulative variable, you can put it in the second parameter of the reduce method and the reduceRight method.

[1, 2, 3, 4, 5].reduce(function (a, b) {
  return a + b;
}, 10);
// 25

The above code specifies that the initial value of parameter a is 10, so the array is accumulated from 10, and the final result is 25. Note that b is traversing from the first member of the array.

Because these two methods can traverse arrays, they can also be used to do some traversal related operations. For example, find the array member with the longest character length.

function findLongest(entries) {
  return entries.reduce(function (longest, entry) {
    return entry.length > longest.length ? entry : longest;
  }, '');
}

findLongest(['aaa', 'bb', 'c']) // "aaa"

In the above code, the parameter function of reduce will use the array member with the longer character length as the cumulative value. This causes the cumulative value to be the member with the longest character length after traversing all members.

The indexOf method returns the first occurrence of the given element in the array, or - 1 if it does not.

var a = ['a', 'b', 'c'];

a.indexOf('b') // 1
a.indexOf('y') // -1

The indexOf method can also accept a second parameter that represents the start of the search.

['a', 'b', 'c'].indexOf('a', 1) // -1

The above code starts to search for character a from position 1, and the result is - 1, indicating no search is found.

The lastIndexOf method returns the last occurrence of the given element in the array, or - 1 if it does not.

var a = [2, 5, 9, 2];
a.lastIndexOf(2) // 3
a.lastIndexOf(7) // -1

Note that these two methods cannot be used to search for the location of NaN, that is, they cannot determine whether array members contain NaN.

[NaN].indexOf(NaN) // -1
[NaN].lastIndexOf(NaN) // -1

This is because within these two methods, the strict equality operator (====) is used for comparison, and NaN is the only value that is not equal to itself.

22 Number

(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' })
// "¥123.00"

23 String

var s1 = 'abc';
var s2 = new String('abc');

typeof s1 // "string"
typeof s2 // "object"

s2.valueOf() // "abc"

The root cause of this phenomenon is that characters with code points greater than 0xFFFF take up four bytes, while JavaScript supports two bytes by default. In this case, 0x20BB7 must be split into two character representations.

String.fromCharCode(0xD842, 0xDFB7)
// "Wei"

slice method is used to take a substring from the original string and return it without changing the original string. Its first parameter is the start position of the substring, and its second parameter is the end position of the substring (excluding the position).

'JavaScript'.slice(0, 4) // "Java"

If the second parameter is omitted, the substring is represented until the end of the original string.

'JavaScript'.slice(4) // "Script"

If the parameter is a negative value, it indicates the position from the end to the reciprocal, that is, the negative value plus the string length.

'JavaScript'.slice(-6) // "Script"
'JavaScript'.slice(0, -6) // "Java"
'JavaScript'.slice(-2, -1) // "p"

If the first parameter is greater than the second, the slice method returns an empty string.

'JavaScript'.slice(2, 1) // ""

The substring method is used to take a substring from the original string and return it without changing the original string, which is very similar to the slice method. Its first parameter represents the start position of the substring, and the second position represents the end position (the returned result does not contain that position).

'JavaScript'.substring(0, 4) // "Java"

If the second parameter is omitted, the substring is represented until the end of the original string.

'JavaScript'.substring(4) // "Script"

If the first parameter is greater than the second, the substring method automatically changes the position of the two parameters.

'JavaScript'.substring(10, 4) // "Script"
// Equate to
'JavaScript'.substring(4, 10) // "Script"

In the above code, you can exchange two parameters of the substring method and get the same result.

If the parameter is negative, the substring method automatically converts the negative number to 0.

'JavaScript'.substring(-3) // "JavaScript"
'JavaScript'.substring(4, -3) // "Java"

In the above code, the parameter - 3 of the second example will automatically become 0, which is equivalent to 'JavaScript'. substring(4, 0). Since the second parameter is smaller than the first parameter, the position will be exchanged automatically, so Java is returned.

Because these rules are counter intuitive, the substring method is not recommended, and slice should be used first.

The substr method is used to take a substring from the original string and return it without changing the original string. It has the same function as slice and substring methods.

The first parameter of the substr method is the start position of the substring (calculated from 0), and the second parameter is the length of the substring.

'JavaScript'.substr(4, 6) // "Script"

If the second parameter is omitted, the substring is represented until the end of the original string.

'JavaScript'.substr(4) // "Script"

If the first parameter is negative, it indicates the character position of the reciprocal calculation. If the second parameter is negative, it is automatically converted to 0, so an empty string is returned.

'JavaScript'.substr(-6) // "Script"
'JavaScript'.substr(4, -1) // ""

The split method can also take a second parameter, which limits the maximum number of members to return to the array.

'a|b|c'.split('|', 0) // []
'a|b|c'.split('|', 1) // ["a"]
'a|b|c'.split('|', 2) // ["a", "b"]
'a|b|c'.split('|', 3) // ["a", "b", "c"]
'a|b|c'.split('|', 4) // ["a", "b", "c"]

24 Math

Here's how to calculate the area of a circle.

var radius = 20;
var area = Math.PI * Math.pow(radius, 2);

Math.random()
The random number generating function of any range is as follows.

function getRandomArbitrary(min, max) {
  return Math.random() * (max - min) + min;
}

getRandomArbitrary(1.5, 6.5)
// 2.4942810038223864

The random integer generating functions of any range are as follows.

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

getRandomInt(1, 6) // 5

25 Date

Any string that can be parsed by the Date.parse() method can be used as an argument.

new Date('2013-2-15')
new Date('2013/2/15')
new Date('02/15/2013')
new Date('2013-FEB-15')
new Date('FEB, 15, 2013')
new Date('FEB 15, 2013')
new Date('February, 15, 2013')
new Date('February 15, 2013')
new Date('15 Feb 2013')
new Date('15, February, 2013')
// Fri Feb 15 2013 00:00:00 GMT+0800 (CST)

There are three ways to convert a Date instance to a string representing the local time.

Date.prototype.toLocaleString(): full local time.
Date.prototype.toLocaleDateString(): local date (excluding hours, minutes and seconds).
Date.prototype.toLocaleTimeString(): local time (excluding MM DD YY).
Here is an example of usage.

var d = new Date(2013, 0, 1);

d.toLocaleString()
// The Chinese version of browser is "12:00:00 am, January 1, 2013"
// English browser is "1/1/2013 12:00:00 AM"

d.toLocaleDateString()
// Chinese version of browser is "January 1, 2013"
// The English browser is "1 / 1 / 2013"

d.toLocaleTimeString()
// The Chinese version of browser is "12:00:00 a.m."
// The English browser is "12:00:00 AM"

26 RegExp

The test method of the regular instance object returns a Boolean value indicating whether the current pattern can match the parameter string.

/cat/.test('cats and dogs') // true

The above code verifies whether the parameter string contains cat, and the result returns true.

If the regular expression has a g modifier, each test method matches backwards from where it ended last time.

var r = /x/g;
var s = '_x_x';

r.lastIndex // 0
r.test(s) // true

r.lastIndex // 2
r.test(s) // true

r.lastIndex // 4
r.test(s) // false

The exec method of the regular instance object to return the matching result. If a match is found, an array is returned, and the member is the substring that matches successfully, otherwise null is returned.

var s = '_x_x';
var r1 = /x/;
var r2 = /y/;

r1.exec(s) // ["x"]
r2.exec(s) // null

Four of the instance methods of string are related to regular expressions.

String.prototype.match(): returns an array whose members are all matching substrings.
String.prototype.search(): search according to the given regular expression, and return an integer to indicate the starting position of the match.
String.prototype.replace(): replace according to the given regular expression and return the replaced string.
String.prototype.split(): splits a string according to the given rules, and returns an array containing the split members.

match

var s = 'abba';
var r = /a/g;

s.match(r) // ["a", "a"]
r.exec(s) // ["a"]

var s = 'abab'
var r = /a/g;

The replace method of the string object can replace the matching value. It takes two parameters, the first is a regular expression, which represents the search pattern, and the second is the replacement content.

str.replace(search, replacement)

If the regular expression does not add the g modifier, it will replace the first matching successful value, otherwise, it will replace all matching successful values.

'aaa'.replace('a', 'b') // "baa"
'aaa'.replace(/a/, 'b') // "baa"
'aaa'.replace(/a/g, 'b') // "bbb"

One application of the replace method is to eliminate the spaces at the beginning and end of a string.

var str = '  #id div.class  ';
str.replace(/^\s+|\s+$/g, '')
// "#id div.class"

The second parameter of the replace method can use the dollar sign $, which refers to the replaced content.

$&: matching substring.
$`: the text before the match result.
$': text after the match result.
$n: the nth group content that matches successfully, n is the natural number starting from 1.
$: US dollar symbol: US dollar symbol: US dollar symbol.

'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
// "world hello"

'abc'.replace('b', '[$`-$&-$\']')
// "a[a-b-c]c"

In the above code, the first example is to swap the matched groups, and the second example is to overwrite the matched values.

The second parameter of the replace method can also be a function, replacing each match with a function return value.

'3 and 5'.replace(/[0-9]+/g, function (match) {
  return 2 * match;
})
// "6 and 10"

var a = 'The quick brown fox jumped over the lazy dog.';
var pattern = /quick|brown|lazy/ig;

a.replace(pattern, function replacer(match) {
  return match.toUpperCase();
});
// The QUICK BROWN fox jumped over the LAZY dog.

As a replacement function for the second parameter of the replace method, multiple parameters can be accepted. Among them, the first parameter is the captured content, and the second parameter is the captured group matching (there are as many group matching as there are corresponding parameters). In addition, two parameters can be added at last. The penultimate parameter is the position of the captured content in the whole string (for example, starting from the fifth position), and the last parameter is the original string. Here is an example of a web template replacement.

var prices = {
  'p1': '$1.99',
  'p2': '$9.99',
  'p3': '$5.00'
};

var template = '<span id="p1"></span>'
  + '<span id="p2"></span>'
  + '<span id="p3"></span>';

template.replace(
  /(<span id=")(.*?)(">)(<\/span>)/g,
  function(match, $1, $2, $3, $4){
    return $1 + $2 + $3 + prices[$2] + $4;
  }
);
// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"

In the capture mode of the above code, there are four brackets, so four group matches will be generated, which are represented by $1 to $4 in the match function. The match function inserts the price into the template.

The split method of the string object splits the string according to the regular rules, and returns an array composed of the separated parts.

str.split(separator, [limit])

This method takes two parameters. The first parameter is a regular expression, which represents the separation rule. The second parameter is the maximum number of members of the returned array.

// Irregular separation
'a,  b,c, d'.split(',')
// [ 'a', '  b', 'c', ' d' ]

// Regular separation, remove extra space
'a,  b,c, d'.split(/, */)
// [ 'a', 'b', 'c', 'd' ]

// Specifies the maximum member of the return array
'a,  b,c, d'.split(/, */, 2)
[ 'a', 'b' ]

27 JSON

The above code converts various types of values into JSON strings.

Note that for strings of the original type, the conversion results are in double quotes.

JSON.stringify('foo') === "foo" // false
JSON.stringify('foo') === "\"foo\"" // true

In the above code, the string foo is converted to "foo". This is because when restoring in the future, inner double quotation marks can let the JavaScript engine know that this is a string, rather than other types of values.

JSON.stringify(false) // "false"
JSON.stringify('false') // "\"false\""

In the above code, if it is not the inner double quotation mark, the engine will not know whether the original value is a Boolean value or a string when restoring in the future.

If an object's attribute is undefined, a function, or an XML object, it is filtered by JSON.stringify.

var obj = {
  a: undefined,
  b: function () {}
};

JSON.stringify(obj) // "{}"

In the above code, the a attribute of obj is undefined, while the b attribute is a function, and the results are filtered by JSON.stringify.

If the members of the array are undefined, functions, or XML objects, the values are converted to null.

var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]"

The JSON.stringify method ignores the object's non traversable properties.

The JSON.stringify method can also accept an array as the second parameter, which specifies the properties to be converted to a string.

var obj = {
  'prop1': 'value1',
  'prop2': 'value2',
  'prop3': 'value3'
};

var selectedProperties = ['prop1', 'prop2'];

JSON.stringify(obj, selectedProperties)
// "{"prop1":"value1","prop2":"value2"}"

In the above code, the second parameter of JSON.stringify method is specified, and only two properties, prop1 and prop2, are transferred.

This white list like array is only valid for the properties of objects and not for arrays.

JSON.stringify(['a', 'b'], ['0'])
// "["a","b"]"

JSON.stringify({0: 'a', 1: 'b'}, ['0'])

The second parameter can also be a function that changes the return value of JSON.stringify.

function f(key, value) {
  if (typeof value === "number") {
    value = 2 * value;
  }
  return value;
}

JSON.stringify({ a: 1, b: 2 }, f)
// '{"a": 2,"b": 4}'

The f function in the above code takes two parameters, namely the key name and key value of the object to be converted. If the key value is a number, multiply it by 2, otherwise return as is.

Note that this handler handles all keys recursively.
In recursive processing, every object processed is the value returned in the previous time.

var o = {a: 1};

function f(key, value) {
  if (typeof value === 'object') {
    return {b: 2};
  }
  return value * 2;
}

JSON.stringify(o, f)
// "{"b": 4}"

The first time the key name is empty, the key value is the entire object o; the second time the key name is a, the key value is 1.

JSON.stringify can also accept a third parameter to increase the readability of the returned JSON string. If it is a number, it means the space added before each attribute (no more than 10); if it is a string (no more than 10 characters), it will be added before each line.

JSON.stringify({ p1: 1, p2: 2 }, null, 2);
/*
"{
  "p1": 1,
  "p2": 2
}"
*/

JSON.stringify({ p1:1, p2:2 }, null, '|-');
/*
"{
|-"p1": 1,
|-"p2": 2
}"
*/

If the incoming string is not in a valid JSON format, the JSON.parse method will report an error.

JSON.parse("'String'") // illegal single quotes
// SyntaxError: Unexpected token ILLEGAL

In the above code, the double quote string is a single quote string, because the single quote string does not conform to JSON format, so an error is reported.

To handle parsing errors, put the JSON.parse method in try In the catch code block.

try {
  JSON.parse("'String'");
} catch(e) {
  console.log('parsing error');
}

The JSON.parse method can accept a handler function as a second parameter, similar to the JSON.stringify method.

function f(key, value) {
  if (key === 'a') {
    return value + 10;
  }
  return value;
}

JSON.parse('{"a": 1, "b": 2}', f)

28 object

Another solution is to determine whether to use the new command inside the constructor. If it is not used, an instance object will be returned directly.

function Fubar(foo, bar) {
  if (!(this instanceof Fubar)) {
    return new Fubar(foo, bar);
  }

  this._foo = foo;
  this._bar = bar;
}

Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1

The principle of new command
When you use the new command, its subsequent functions perform the following steps in turn.

Create an empty object as an instance of the object to be returned.
Point the prototype of the empty object to the prototype property of the constructor.
Assign this empty object to this keyword inside the function.
Start executing code inside the constructor.
In other words, within the constructor, this refers to a newly generated empty object. All operations on this will occur on this empty object. The reason why constructors are called "constructors" is that the purpose of this function is to operate an empty object (i.e. this object) and "construct" it as needed.

If there is a return statement inside the constructor, and the return is followed by an object, the new command will return the object specified in the return statement; otherwise, it will return this object regardless of the return statement.

var Vehicle = function () {
  this.price = 1000;
  return 1000;
};

(new Vehicle()) === 1000
// false

In the above code, the return statement of the constructor Vehicle returns a value. At this time, the new command ignores the return statement and returns the "this" object after "construction".

However, if the return statement returns a new object that has nothing to do with this, the new command returns the new object instead of this object. This needs special attention.

var Vehicle = function (){
  this.price = 1000;
  return { price: 2000 };
};

(new Vehicle()).price
// 2000

In the above code, the return statement of the constructor Vehicle returns a new object. The new command returns this object, not this object.

On the other hand, if you use the new command for a normal function (a function without this keyword inside), an empty object is returned.

function getMessage() {
  return 'this is a message';
}

var msg = new getMessage();

msg // {}
typeof msg // "object"

In the above code, getMessage is a normal function that returns a string. Use the new command on it to get an empty object. This is because the new command always returns an object, either an instance object or an object specified by the return statement. In this case, the return statement returns a string, so the new command ignores the statement.

The new command simplifies the internal process, which can be represented by the following code.

function _new(/* Constructor */ constructor, /* constructors parameters  */ params) {
  // Convert arguments object to array
  var args = [].slice.call(arguments);
  // Get constructor
  var constructor = args.shift();
  // Create an empty object that inherits the prototype property of the constructor
  var context = Object.create(constructor.prototype);
  // Execute constructor
  var result = constructor.apply(context, args);
  // If the returned result is an object, it will be returned directly; otherwise, it will be returned to the context object
  return (typeof result === 'object' && result != null) ? result : context;
}

// Example
var actor = _new(Person, 'Zhang San', 28);

this.x in the function body refers to the x of the current running environment.

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// Separate execution
f() // 1

// obj environment execution
obj.f() // 2

In the above code, function f is executed in the global environment, this.x points to x in the global environment, and this.x points to obj.x in obj environment.

Object method

If the method of an object contains this, the point of this is the object where the method runs. When this method is assigned to another object, it changes the direction of this.

However, this rule is not easy to grasp. Look at the code below.

var obj ={
  foo: function () {
    console.log(this);
  }
};

obj.foo() // obj

In the above code, when the obj.foo method is executed, its internal this points to obj.

However, the following usage will change the direction of this.

// Situation 1
(obj.foo = obj.foo)() // window
// Situation two
(false || obj.foo)() // window
// Situation three
(1, obj.foo)() // window

In the above code, obj.foo is a value. When this value is called, the running environment is not obj, but the global environment, so this no longer points to obj.

It can be understood that inside the JavaScript engine, obj and obj.foo are stored in two memory addresses, called address one and address two. When obj.foo() is called in this way, it is called from address one to address two. Therefore, the running environment of address two is address one, and this points to obj. However, in the above three cases, address 2 is taken directly for calling. In this case, the running environment is the global environment, so this points to the global environment. The above three cases are equivalent to the following code.

// Situation 1
(obj.foo = function () {
  console.log(this);
})()
// Equate to
(function () {
  console.log(this);
})()

// Situation two
(false || function () {
  console.log(this);
})()

// Situation three
(1, function () {
  console.log(this);
})()

If this method is not in the first layer of the object, then this only points to the object of the current layer, and does not inherit the upper layer.

var a = {
  p: 'Hello',
  b: {
    m: function() {
      console.log(this.p);
    }
  }
};

a.b.m() // undefined

In the above code, the a.b.m method is in the second layer of the a object. this inside the method does not point to a, but to a.b, because the following code is actually executed.

var b = {
  m: function() {
   console.log(this.p);
  }
};

var a = {
  p: 'Hello',
  b: b
};

(a.b).m() // Equivalent to b.m()

If you want to achieve the desired effect, only write as follows.

var a = {
  b: {
    m: function() {
      console.log(this.p);
    },
    p: 'Hello'
  }
};

If the method inside the nested object is assigned to a variable, this will still point to the global object.

var a = {
  b: {
    m: function() {
      console.log(this.p);
    },
    p: 'Hello'
  }
};

var hello = a.b.m;
hello() // undefined

In the above code, m is a method inside a multi-level object. For simplicity, assign it to the hello variable. When the result is called, this points to the top-level object. To avoid this problem, you can only assign m's object to hello, so that when you call, the point of this will not change.

var hello = a.b;
hello.m() // Hello

The array's map and foreach methods allow you to provide a function as an argument. This should not be used inside this function.

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    });
  }
}

o.f()
// undefined a1
// undefined a2

In the above code, this in the callback function of foreach method actually points to the window object, so the value of o.v cannot be obtained. The reason is the same as the multi-layer this in the previous section, that is, the inner layer this does not point to the external, but to the top-level object.

One way to solve this problem is to use the intermediate variable to fix this.

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
}

o.f()
// hello a1
// hello a2

Another method is to use this as the second parameter of foreach method and fix its running environment.

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    this.p.forEach(function (item) {
      console.log(this.v + ' ' + item);
    }, this);
  }
}

o.f()
// hello a1
// hello a2

The dynamic switch of this, while creating great flexibility for JavaScript, also makes programming difficult and fuzzy. Sometimes, you need to fix this to avoid unexpected situations. JavaScript provides three methods: call, apply and bind to switch / fix the direction of this.

Function.prototype.call()
The call method of the function instance can specify the direction of this within the function (that is, the scope in which the function is executed), and then call the function in the specified scope.

var obj = {};

var f = function () {
  return this;
};

f() === window // true
f.call(obj) === obj // true

In the above code, when the global environment runs the function f, this points to the global environment (the browser is a window object); the call method can change the direction of this, specify that this points to the object obj, and then run the function f in the scope of the object obj

The call method can also accept multiple parameters.

func.call(thisValue, arg1, arg2, ...)

The first parameter of the call is the object to which this refers, and the latter is the parameter required for function call.

function add(a, b) {
  return a + b;
}

add.call(this, 1, 2) // 3

Function.prototype.apply()
The apply method is similar to the call method in that it changes the direction of this and then calls the function. The only difference is that it receives an array as a function's execution parameter in the following format.

func.apply(thisValue, [arg1, arg2, ...])
function f(x, y){
  console.log(x + y);
}

f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

In the above code, the f function originally accepts two parameters. After using the apply method, it becomes possible to accept an array as a parameter.

Using this, we can make some interesting applications.

(1) Find the largest element of the array

JavaScript does not provide a function to find the largest element of an array. Using the apply method in combination with the Math.max method, you can return the largest element of the array.

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

(2) Change the empty element of the array to undefined

Through the apply method, use the Array constructor to change the empty elements of the Array into undefined.

Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]

The difference between empty elements and undefined is that the forEach method of an array skips empty elements, but undefined elements. Therefore, when traversing internal elements, different results will be obtained.

var a = ['a', , 'b'];

function print(i) {
  console.log(i);
}

a.forEach(print)
// a
// b

Array.apply(null, a).forEach(print)
// a
// undefined
// b

(3) Convert array like objects

In addition, by using slice method of array object, an array like object (such as arguments object) can be transformed into a real array.

Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]

The parameters of the apply method in the above code are all objects, but the returned results are arrays, which serves the purpose of converting objects into arrays. As you can see from the above code, the precondition for this method to work is that the object to be processed must have the length attribute and the corresponding number key.

(4) Object to bind callback function

The previous button click event example can be rewritten as follows.

var o = new Object();

o.f = function () {
  console.log(this === o);
}

var f = function (){
  o.f.apply(o);
  // Or o.f.call(o);
};

// How to write jQuery
$('#button').on('click', f);

Bind can also take more parameters and bind them to the parameters of the original function.

var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5) // 20

In the above code, in addition to binding this object, the bind method also binds the first parameter x of the add function to 5, and then returns a new function newAdd, which can run as long as it accepts another parameter y.

If the first parameter of the bind method is null or undefined, this is bound to the global object. When the function runs, this points to the top-level object (the browser is window).

function add(x, y) {
  return x + y;
}

var plus5 = add.bind(null, 5);
plus5(10) // 15

In the above code, there is no this inside the function add. The main purpose of using bind method is to bind parameter x. every time you run the new function plus5 in the future, you only need to provide another parameter y. Moreover, because there is no this in add, the first parameter of bind is null, but if it is another object here, it has no effect.

There are some use considerations for the bind method.

(1) Return a new function every time

Every time the bind method runs, it returns a new function, which causes some problems. For example, when listening to an event, you can't write it as follows.

element.addEventListener('click', o.m.bind(o));

In the above code, click event binds an anonymous function generated by bind method. As a result, you cannot unbind, so the following code is invalid.

element.removeEventListener('click', o.m.bind(o));

The correct way is to write as follows:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

(2) Use with callback function

Callback function is one of the most common patterns in JavaScript, but a common mistake is to treat the method containing this as a callback function directly. The solution is to bind counter.inc to counter using bind method.

var counter = {
  count: 0,
  inc: function () {
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
  callback();
}

callIt(counter.inc.bind(counter));
counter.count // 1

In the above code, the callIt method calls the callback function. At this time, if counter.inc is passed in directly, this inside counter.inc will point to the global object when calling. After using bind method to bind counter.inc to counter, this problem will not occur. This always points to counter.

There is also a more subtle situation, that is, some array methods can accept a function as a parameter. These functions point to this inside, which is also likely to make mistakes.

var obj = {
  name: 'Zhang San',
  times: [1, 2, 3],
  print: function () {
    this.times.forEach(function (n) {
      console.log(this.name);
    });
  }
};

obj.print()
// No output

In the above code, this of this.times in obj.print points to obj, which is OK. However, this.name in the callback function of forEach method points to the global object, so there is no way to get the value. With a little change, you can see better.

obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this === window);
  });
};

obj.print()
// true
// true
// true

To solve this problem, bind this method.

obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this.name);
  }.bind(this));
};

obj.print()
// Zhang San
// Zhang San
// Zhang San

(3) Use in combination with call method

Using bind method, we can rewrite the use of some JavaScript native methods, taking slice method of array as an example.

[1, 2, 3].slice(0, 1) // [1]
// Equate to
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

In the above code, slice method of array cuts another array from [1, 2, 3] according to the specified position and length. The essence of this is to call the Array.prototype.slice method on [1, 2, 3], so you can use the call method to express this process and get the same result.

The call method essentially calls the Function.prototype.call method, so the above expression can be rewritten with the bind method.

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
The meaning of the above code is to change the Array.prototype.slice into the object where the Function.prototype.call method is located. When calling, it becomes Array.prototype.slice.call. Similar writing can also be used for other array methods.

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]

pop(a)
a // [1, 2, 3]
If you go further, binding the Function.prototype.call method to the Function.prototype.bind object means that the call form of bind can also be rewritten.

function f() {
console.log(this.v);
}

var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123
The meaning of the above code is to bind the Function.prototype.bind method to Function.prototype.call, so the bind method can be used directly without using on the function instance.

29 object inheritance

If you let the prototype property of the constructor point to an array, it means that the instance object can call the array method.

var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

In the above code, mine is the instance object of the constructor MyArray. Because MyArray.prototype points to an Array instance, mine can call Array methods (these methods are defined on the prototype object of the Array instance). The last line of instanceof expression is used to compare whether an object is an instance of a constructor. The result is to prove that mine is an instance of Array. For detailed explanation of instanceof operator, see the following.

constructor property#
The prototype object has a constructor property, which by default points to the constructor where the prototype object is located.

function P() {}
P.prototype.constructor === P // true

Therefore, when you modify the prototype object, you generally need to change the direction of the constructor property at the same time.

// Bad writing
C.prototype = {
  method1: function (...) { ... },
  // ...
};

// Good writing
C.prototype = {
  constructor: C,
  method1: function (...) { ... },
  // ...
};

// Better writing
C.prototype.method1 = function (...) { ... };

In the above code, you can either point the constructor property back to the original constructor or only add methods to the prototype object, which ensures that the instanceof operator is not distorted.

If you are not sure what function the constructor property is, there is another way: through the name property, get the name of the constructor from the instance.

function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"

Since any Object (except null) is an instance of Object, the instanceof operator can determine whether a value is a non null Object.

var obj = { foo: 123 };
obj instanceof Object // true
null instanceof Object // false

In the above code, except for null, the operation result of instanceOf Object of other objects is true.

The principle of instanceof is to check whether the prototype property of the right constructor is on the prototype chain of the left object. There is a special case where there are only null objects in the prototype chain of the object on the left. At this time, the instanceof judgment will be distorted.

var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false

In the above code, Object.create(null) returns a new Object, obj, whose prototype is null (see the following for details of Object.create). The prototype property of the Object constructor on the right is not in the prototype chain on the left, so instanceof thinks that obj is not an instance of Object. However, as long as the prototype of an Object is not null, the judgment of the instanceof operator will not be distorted.

One use of the instanceof operator is to determine the type of value.

var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true

In the above code, the instanceof operator judges that the variable x is an array and the variable y is an object.

Note that the instanceof operator can only be used on objects, not values of the original type.

var s = 'hello';
s instanceof String // false

In the above code, the String is not an instance of the String object (because the String is not an object), so it returns false.

In addition, the instanceof operator always returns false for undefined and null.

undefined instanceof Object // false
null instanceof Object // false

By using the instanceof operator, we can also solve the problem of forgetting to add the new command when calling the constructor.

function Fubar (foo, bar) {
  if (this instanceof Fubar) {
    this._foo = foo;
    this._bar = bar;
  } else {
    return new Fubar(foo, bar);
  }
}

Having one constructor inherit another is a very common requirement. This can be implemented in two steps. The first step is to call the constructor of the parent class in the constructor of the child class.

function Sub(value) {
Super.call(this)
this.prop = value
}

In the above code, Sub is the constructor of the subclass, and this is the instance of the subclass. Calling the constructor Super of the parent class on the instance will make the child class instance have the properties of the parent class instance.

The second step is to make the prototype of the subclass point to the prototype of the parent, so that the subclass can inherit the prototype of the parent.

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

In the above code, Sub.prototype is the prototype of the subclass, which should be assigned as Object.create(Super.prototype), rather than directly equal to Super.prototype. Otherwise, the operation of Sub.prototype in the next two lines will be modified together with the prototype Super.prototype of the parent class.

Another way to write it is that Sub.prototype is equal to a parent instance.

Sub.prototype = new Super();

The above method also has the effect of inheritance, but the subclass will have the method of the parent instance. Sometimes, this may not be what we need, so it's not recommended.

For example, here is a Shape constructor.

function Shape() {
  this.x = 0;
  this.y = 0;
}

Shape.prototype.move = function (x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

We need to have the Rectangle constructor inherit the Shape.

// First, the subclass inherits the instance of the parent class
function Rectangle() {
  Shape.call(this); // Call parent constructor
}
// Another way of writing
function Rectangle() {
  this.base = Shape;
  this.base();
}

// Second, the subclass inherits the prototype of the parent class
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

In this way, the instanceof operator will return true to the constructors of the subclass and the parent class.

var rect = new Rectangle();

rect instanceof Rectangle  // true
rect instanceof Shape  // true

In the above code, the subclass is the whole inheritance parent class. Sometimes you only need to inherit a single method, so you can use the following method.

ClassB.prototype.print = function() {
ClassA.prototype.print.call(this);
// some code
}
In the above code, the print method of subclass B calls the print method of parent class A, and then deploys its own code. This is equivalent to inheriting the print method of parent class A.

Module is the encapsulation of a set of attributes and methods to achieve specific functions.

The simple way is to write the module as an object, and all module members are put into the object.

var module1 = new Object({
 _count : 0,
 m1 : function (){
  //...
 },
 m2 : function (){
   //...
 }
});

The above functions m1 and m2 are encapsulated in the module1 object. When used, the property of this object is called.

module1.m1();

However, such a writing exposes all module members, and the internal state can be overridden externally. For example, external code can directly change the value of an internal counter.

module1._count = 5;

Encapsulating private variables: the writing of constructors
We can use constructors to encapsulate private variables.

function StringBuilder() {
  var buffer = [];

  this.add = function (str) {
     buffer.push(str);
  };

  this.toString = function () {
    return buffer.join('');
  };

}

In the above code, buffer is the private variable of the module. Once the instance object is generated, the buffer cannot be accessed directly by the external. However, this method encapsulates private variables in the constructor, resulting in the integration of the constructor and the instance object, which always exists in memory and cannot be cleared after use. This means that the constructor has two functions, one is to shape the instance object, the other is to save the data of the instance object, which violates the principle that the constructor and the instance object are separated in data (that is, the data of the instance object should not be saved outside the instance object). At the same time, it is very memory intensive.

function StringBuilder() {
  this._buffer = [];
}

StringBuilder.prototype = {
  constructor: StringBuilder,
  add: function (str) {
    this._buffer.push(str);
  },
  toString: function () {
    return this._buffer.join('');
  }
};

This method puts the private variables into the instance object, the advantage is that it looks more natural, but its private variables can be read and written from the outside, which is not very safe.

Encapsulating private variables: writing immediate execution functions
Another way is to use "immediately invoked function expression" (IIFE) to encapsulate related attributes and methods in a function scope, so as to avoid exposing private members.

var module1 = (function () {
 var _count = 0;
 var m1 = function () {
   //...
 };
 var m2 = function () {
  //...
 };
 return {
  m1 : m1,
  m2 : m2
 };
})();

Using the above method, external code cannot read the internal count variable.

console.info(module1._count); //undefined

The above module1 is the basic writing method of JavaScript module. Next, we will process this writing method.

Amplification mode of module
If a module is large, it must be divided into several parts, or one module needs to inherit another module, then it is necessary to adopt "amplification mode"
(augmentation).

var module1 = (function (mod){
 mod.m3 = function () {
  //...
 };
 return mod;
})(module1);

The above code adds a new method m3() to the module1 module and returns the new module1 module.

In the browser environment, each part of the module is usually obtained from the Internet, sometimes it is impossible to know which part will be loaded first. If you use the above method, the first part of the execution may load an empty object that does not exist, then you need to use the "wide amplification mode".

var module1 = (function (mod) {
 //...
 return mod;
})(window.module1 || {});

Compared with zoom in mode, wide zoom in mode means that the parameters of execute function immediately can be empty objects.

Enter global variables
Independence is an important feature of a module. It is better not to directly interact with other parts of the program.

In order to call global variables inside a module, other variables must be explicitly entered into the module.

var module1 = (function ($, YAHOO) {
 //...
})(jQuery, YAHOO);

The above module1 module needs to use jQuery library and YUI library, so the two libraries (actually two modules) are used as parameters to input module1. In addition to ensuring the independence of the modules, it also makes the dependency between the modules become obvious.

Immediate function execution can also act as a namespace.

(function($, window, document) {

  function go(num) {
  }

  function handleEvents() {
  }

  function initialize() {
  }

  function dieCarouselDie() {
  }

  //attach to the global scope
  window.finalCarousel = {
    init : initialize,
    destroy : dieCarouselDie
  }

})( jQuery, window, document );

In the above code, the finalCarousel object is output to the global, and the init and destroy interfaces are exposed externally. The internal methods go, handleEvents, initialize and dieCarouselDie are all external and cannot be called.

30 related methods of object objects

// The prototype of an empty object is Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true

// The prototype of Object.prototype is null
Object.getPrototypeOf(Object.prototype) === null // true

// The prototype of a function is Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true

The Object.setPrototypeOf method sets the prototype for the parameter object and returns the parameter object. It takes two parameters, the first is an existing object and the second is a prototype object.

var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);

Object.getPrototypeOf(a) === b // true
a.x // 1

In the above code, the Object.setPrototypeOf method sets the prototype of object a as object b, so a can share the properties of b.

The new command can be simulated using the Object.setPrototypeOf method.

var F = function () {
  this.foo = 'bar';
};

var f = new F();
// Equate to
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);

In the above code, the new command creates an instance object, which can be divided into two steps. The first step is to set the prototype of an empty object as the prototype property of the constructor (F.prototype in the above example); the second step is to bind the empty object with this inside the constructor, and then execute the constructor

Object.create()
A common way to generate an instance object is to use the new command to have the constructor return an instance. But many times, you can only get one instance object, which may not be generated by the build function at all. Can you generate another instance object from one instance object?

JavaScript provides the Object.create method to meet this requirement. This method takes an object as a parameter, and then takes it as a prototype to return an instance object. This instance completely inherits the properties of the prototype object.

// Prototype object
var A = {
  print: function () {
    console.log('hello');
  }
};

// Instance object
var B = Object.create(A);

Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true

In the above code, the Object.create method takes the a object as the prototype and generates the B object. B inherits all the properties and methods of A.

In fact, the Object.create method can be replaced with the following code.

if (typeof Object.create !== 'function') {
  Object.create = function (obj) {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

The above code shows that the essence of the Object.create method is to create a new empty constructor F, then let the F.prototype property point to the parameter object obj, and finally return an instance of F, so that the instance can inherit the properties of obj.

The new objects generated in the following three ways are equivalent.

var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();

If you want to generate an object that does not inherit any properties (such as no toString and valueOf methods), you can set the parameter of Object.create to null.

var obj = Object.create(null);

obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'

The object generated by the Object.create method inherits the constructor of its prototype object.

function A() {}
var a = new A();
var b = Object.create(a);

b.constructor === A // true
b instanceof A // true

In the above code, the prototype of b object is a object, so it inherits the constructor a of a object.

Comparison of methods for obtaining prototype objects
As mentioned earlier, the "proto" property points to the prototype object of the current object, that is, the prototype property of the constructor.

var obj = new Object();

obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true

The above code first creates a new Object obj, its "proto" property, pointing to the prototype property of the constructor (Object or obj.constructor).

Therefore, there are three ways to get the prototype object of the instance object obj.

obj.proto
obj.constructor.prototype
Object.getPrototypeOf(obj)
Of the above three methods, the first two are not very reliable. __The proto property only needs to be deployed by the browser. Other environments can not be deployed. obj.constructor.prototype may fail when the prototype object is changed manually.

var P = function () {};
var p = new P();

var C = function () {};
C.prototype = p;
var c = new C();

c.constructor.prototype === p // false

In the above code, the prototype object of constructor C is changed to p, but the c.constructor.prototype of instance object does not point to p. Therefore, when changing the prototype object, the constructor property should be set at the same time.

C.prototype = p;
C.prototype.constructor = C;

var c = new C();
c.constructor.prototype === p // true

Therefore, it is recommended to use the third Object.getPrototypeOf method to get the prototype object.

Copy of object
If you want to copy an object, you need to do the following two things.

Make sure that the copied object has the same prototype as the original object.
Ensure that the copied object has the same instance properties as the original object.
The following is the object copy function implemented according to the above two points.

function copyObject(orig) {
  var copy = Object.create(Object.getPrototypeOf(orig));
  copyOwnPropertiesFrom(copy, orig);
  return copy;
}

function copyOwnPropertiesFrom(target, source) {
  Object
    .getOwnPropertyNames(source)
    .forEach(function (propKey) {
      var desc = Object.getOwnPropertyDescriptor(source, propKey);
      Object.defineProperty(target, propKey, desc);
    });
  return target;
}

Another simpler way is to use ES2017 to introduce the standard Object.getOwnPropertyDescriptors method.

function copyObject(orig) {
  return Object.create(
    Object.getPrototypeOf(orig),
    Object.getOwnPropertyDescriptors(orig)
  );
}

The code above wants to capitalize the characters as soon as the user enters the text. But in fact, it can only change the characters before the current input to uppercase, because the browser has not received the new text at this time, so this.value cannot get the latest character. The above code will only work if you overwrite it with setTimeout.

document.getElementById('input-box').onkeypress = function() {
  var self = this;
  setTimeout(function() {
    self.value = self.value.toUpperCase();
  }, 0);
}

The above code puts the code into setTimeout, which can be triggered after the browser receives the text.

Because setTimeout(f, 0) actually means that tasks are executed in the earliest available free time of browser, those tasks with large amount of calculation and long time-consuming are often put into several small parts and executed in setTimeout(f, 0).

var div = document.getElementsByTagName('div')[0];

// Write a way
for (var i = 0xA00000; i < 0xFFFFFF; i++) {
  div.style.backgroundColor = '#' + i.toString(16);
}

// Writing two
var timer;
var i=0x100000;

function func() {
  timer = setTimeout(func, 0);
  div.style.backgroundColor = '#' + i.toString(16);
  if (i++ == 0xFFFFFF) clearTimeout(timer);
}

timer = setTimeout(func, 0);

There are two ways to write the above code, which are to change the background color of a web page element. Writing method will cause browser "blocking". Because JavaScript execution speed is much faster than DOM, it will cause a large number of DOM operations "piling up", while writing method 2 will not, which is the advantage of setTimeout(f, 0).

Another example of using this technique is code highlighting. If the code block is large, one-time processing may cause a lot of pressure on performance, then divide it into small blocks, processing one block at a time, such as writing as setTimeout(highlightNext, 50), the performance pressure will be reduced.

31document

document.createEvent() #
The document.createEvent method generates an Event object (Event instance), which can be used by the element.dispatchEvent method to trigger the specified Event.

var event = document.createEvent(type);

The parameter of the document.createEvent method is the event type, such as UIEvents, MouseEvents, MutationEvents, HTML events.

var event = document.createEvent('Event');
event.initEvent('build', true, true);
document.addEventListener('build', function (e) {
  console.log(e.type); // "build"
}, false);
document.dispatchEvent(event);

The above code creates a new event instance called build, and then triggers the event.

document.addEventListener(),document.removeEventListener(),document.dispatchEvent()
These three methods are used to handle the events of the document node. They are all inherited from the EventTarget interface. Please refer to the chapter "EventTarget interface" for details.

// Add event listener
document.addEventListener('click', listener, false);

// Remove event listener
document.removeEventListener('click', listener, false);

// Trigger event
var event = new Event('click');
document.dispatchEvent(event);

document.queryCommandSupported()

The document.queryCommandSupported() method returns a Boolean value indicating whether the browser supports a command of document.execCommand().

if (document.queryCommandSupported('SelectAll')) {
  console.log('The browser supports selecting all contents of the editable region');
}

document.queryCommandEnabled()

The document.queryCommandEnabled() method returns a Boolean value indicating whether a command of document.execCommand() is currently available. For example, the bold command is only available if there is a text check, and not if there is no text selected.

// HTML code is
// <input type="button" value="Copy" onclick="doCopy()">

function doCopy(){
  // Does the browser support the copy command (the selection is copied to the clipboard)
  if (document.queryCommandSupported('copy')) {
    copyText('Hello');
  }else{
    console.log('Browser does not support');
  }
}

function copyText(text) {
  var input = document.createElement('textarea');
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();

  // Whether there is currently selected text
  if (document.queryCommandEnabled('copy')) {
    var success = document.execCommand('copy');
    input.remove();
    console.log('Copy Ok');
  } else {
    console.log('queryCommandEnabled is false');
  }
}

In the above code, first determine whether the browser supports the copy command (the selected content in the editable area is allowed to be copied to the clipboard). If so, create a new temporary text box with the content "hello" written in it and select it. Then, judge whether the selection is successful. If it is successful, copy "hello" to the clipboard, and then delete the temporary text box.

Element.getBoundingClientRect() #
The Element.getBoundingClientRect method returns an object, providing information such as the size and location of the current element node, basically all the information of the CSS box model.

var rect = obj.getBoundingClientRect();

In the above code, the rect object returned by the getBoundingClientRect method has the following properties (all read-only).

x: The abscissa of the upper left corner of the element relative to the viewport
y: The ordinate of the upper left corner of the element relative to the viewport
Height: element height
Width: element width
Left: the abscissa of the upper left corner of the element relative to the viewport, equal to the x attribute
Right: the abscissa of the right boundary of the element relative to the viewport (equal to x + width)
Top: the ordinate of the top of the element relative to the viewport, equal to the y attribute
Bottom: the ordinate of the bottom of the element relative to the viewport (equal to y + height)
Because the position of the element relative to the viewport changes as the page scrolls, the four attribute values representing the position are not fixed. If you want to get the absolute position, you can add the left attribute to window.scrollX and the top attribute to window.scrollY.

Note that all properties of the getBoundingClientRect method count the border as part of the element. That is to say, it is calculated from each point of the outer edge of the border. Therefore, width and height include the element itself + padding + border.

In addition, the above attributes are inherited from the prototype. Object.keys will return an empty array, which should be noted.

var rect = document.body.getBoundingClientRect();
Object.keys(rect) // []

In the above code, the rect object does not have its own properties, while the Object.keys method only returns the properties of the object itself, so it returns an empty array.

Element.getClientRects()
The Element.getClientRects method returns an array like object, which contains all the rectangles formed by the current element on the page (so the Rect in the method name is plural). Each rectangle has six attributes: bottom, height, left, right, top and width, which represent their four coordinates relative to the viewport, as well as their height and width.

For box elements (such as

and

), only one member of the element is returned by the method. For inline elements (such as,), the number of members of the object returned by the method depends on how many lines the element occupies on the page. This is the main difference between it and the Element.getBoundingClientRect() method, which always returns a rectangle for elements within a row.

Hello World Hello World Hello World
The above code is an in line element. If it occupies three lines on the page, the object returned by the getClientRects method has three members. If it occupies one line on the page, the object returned by the getClientRects method has only one member.

var el = document.getElementById('inline');
el.getClientRects().length // 3
el.getClientRects()[0].left // 8
el.getClientRects()[0].right // 113.908203125
el.getClientRects()[0].bottom // 31.200000762939453
el.getClientRects()[0].height // 23.200000762939453
el.getClientRects()[0].width // 105.908203125

This method is mainly used to determine whether the elements in the line wrap or not, and whether the elements in the line wrap or not

31 incident

If you want the event to reach a node and not propagate, you can use the stopPropagation method of the event object.

// Once the event propagates to the p element, it no longer propagates downward
p.addEventListener('click', function (event) {
  event.stopPropagation();
}, true);

// When the event bubbles to the p element, it no longer bubbles up
p.addEventListener('click', function (event) {
  event.stopPropagation();
}, false);

In the above code, the stopPropagation method stops the event propagation in the capture stage and the bubbling stage respectively.

However, the stopPropagation method will only prevent the event from propagating and not triggering

Listener function for other click events of the node. That is to say, it is not to cancel the click event completely.

p.addEventListener('click', function (event) {
  event.stopPropagation();
  console.log(1);
});

p.addEventListener('click', function(event) {
  // Will trigger
  console.log(2);
});

In the above code, the p element binds two listening functions for the click event. The stopPropagation method can only prevent the propagation of this event and cannot cancel it. Therefore, the second listener function will be triggered. The output will be 1, then 2.

If you want to cancel the event completely and no longer trigger the listening functions of all subsequent click s, you can use the stopImmediatePropagation method.

p.addEventListener('click', function (event) {
  event.stopImmediatePropagation();
  console.log(1);
});

p.addEventListener('click', function(event) {
  // Not triggered
  console.log(2);
});

Because the event will propagate to the parent node in the bubbling phase, the listening function of the child node can be defined on the parent node, and the listening function of the parent node can uniformly handle the events of multiple child elements. This method is called event delegation.

var ul = document.querySelector('ul');

ul.addEventListener('click', function (event) {
  if (event.target.tagName.toLowerCase() === 'li') {
    // some code
  }
});

32 Worker

Then, read the script embedded in the page and use Worker to process it.

var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

worker.onmessage = function (e) {
  // e.data === 'some message'
};

In the above code, first convert the script code embedded in the web page to a binary object, then generate a URL for the binary object, and then let the Worker load the URL. In this way, the main thread and Worker code are on the same web page.

55 original articles published, praised 12, visited 60000+
Private letter follow

Posted by inutero on Sun, 19 Jan 2020 02:45:14 -0800