The cause of

Read and learn Effective JavaScript. With my own reading and understanding, I will focus on recording the essence of the content and typesetting the content, so as to facilitate my own review and communication in the future.

Due to the large number of contents, it is divided into each chapter to write the article, and the number of articles in each chapter is different, so each learning notes article shall be subject to the chapter.

Suitable for fragmented reading, compact reading friends. Try to let the little friends finish the series === 85+% of the whole book.

preface

Content overview

  • In the first chapter, beginners can get familiar with JavaScript and understand primitive type, implicit cast, encoding type and other concepts in JavaScript.
  • Chapter 2 focuses on JavaScript variable scope advice, not only explaining how to do it, but also explaining the reasons behind it to help readers understand it better.
  • Chapters 3 and 4 cover functions, objects, and stereotypes, which are at the heart of what sets JavaScript apart from other languages.
  • In chapter 5, the author introduces two common types, array and dictionary, which are easy to be confused, and gives some suggestions to avoid falling into some traps.
  • Chapter 6 covers library and API design;
  • Chapter 7 covers parallel programming, which is a necessary step toward becoming a JavaScript expert

Chapter 3 “Using Functions”

Functions in JavaScript provide the programmer with both primary abstraction and implementation mechanisms. Functions can also independently emulate many different features found in other languages, such as procedures, methods, constructors, classes, and modules. Therefore, it is inevitable to use functions efficiently in different environments.

Rule 18: Understand the difference between function calls, method calls, and constructor calls

In JavaScript, they are just three different usage patterns for a single constructed object.

A function call

function hello(username) {
  return "hello" + username;
}
hello("Ling Mu");	// "hello Ling Mu"
Copy the code

Simply call the Hello function and bind the given argument to the name parameter

The method call

const obj = {
  username: "Ling Mu".hello: function() {
    return "hello" + this.username; }}; obj.hello();// "hello Ling Mu"

Copy the code

The expression obj.hello() looks for a property named Hello in an obj object that happens to be the obj.hello function, which is the method call.

The binding of this is determined by the expression itself, and the object bound to this is called the call receiver. The following example

const obj2 = {
  username: "Ling Mu2".hello: obj.hello
};
obj2.hello();	// "hello Ling Mu2"
Copy the code

The expression obj2.hello() looks for a property named hello in an obj2 object, which happens to be the obj. Hello function, but the receiver is an obj2 object. Typically, calling a method through an object looks up that method and makes that object its receiver.

If the call is a non-method function, the global object will be the receiver:

function hello() {
  return "hello" + this.username;
}
hello();	// "hello undefined"
Copy the code

Undefined if there is no name attribute in the global object. Binding this to a global object in a global scope is also problematic, so ES5 strictly sets the default binding value of this to undefined

function hello() {
  "use strict";
  return "hello" + this.username;
}
hello();	// error: cannnot read property "usename" of undefined
Copy the code

Constructor call

Functions are used by constructors and, like methods and pure functions, are defined by the function operator.

function User(name, password) {
  this.name = name;
  this.password = password;
}
const u = new User("Ling Mu"."123456");
u.name; 	// "Ling Mu"
Copy the code

Unlike function and method calls, constructor calls take a brand new object as the value of this variable and implicitly return the new object as the result of the call. The constructor’s primary responsibility is to initialize the new object.

conclusion

  • A method invocation takes as the call receiver the object whose method properties are being looked up.
  • Function calls take the global object (undefined in strict mode) as their receiver, and generally rarely use function call syntax to call methods.
  • The constructor needs to be called with the new operator and produces a new object as its receiver.

Rule 19: Master higher-order functions

Higher-order functions

Developing neat and elegant functions often makes code simpler, while higher-order functions, which take functions as arguments or return values, are a powerful and expressive idiom that is also widely used in JavaScript programs.

For simple operations like converting an array of strings, use loops:

const names = ["Lin"."Mu"."Qiqiu"];
const upper = [];
for (let i = 0, n = names.length; i < n; i++) {
  upper[i] = names[i].toUpperCase();
}
upper;	// ["LIN", "MU", "QIQIU"];
Copy the code

The higher-order function and its implementation are:

const names = ["Lin"."Mu"."Qiqiu"];
const upper = names.map(() = >name.toUpperCase();) ; upper;// ["LIN", "MU", "QIQIU"];
Copy the code

Using the map method of array traversal, loops can be eliminated completely and the element-by-element conversion can be achieved using only a local function.

example

The signal to introduce higher-order function abstraction is the occurrence of duplicate or similar code.

For example, suppose we find that part of the program uses English letters to construct a string.

const aIndex = 'a'.charCodeAt(0);		/ / 97
let alphabet = ' ';
for (let i = 0; i < 26; i++) {
  alphabet += String.fromCharCode(aIndex + i);
}
alphabet;	// "abcdefghijklmnopqrstuvwxyz"
Copy the code

At the same time, another part of the program generates a string containing a number.

let digits = ' ';
for (let i = 0; i < 10; i++) {
	digits += i;
}
digits;		/ / "0123456789"
Copy the code

In addition, a random string is created elsewhere in the program.

const aIndex = 'a'.charCodeAt(0);		/ / 97
let random = ' ';
for (let i = 0; i < 8; i++) {
	random += String.fromCharCode(Math.floor(Math.random() * 26) + aIndex);
}
random;		// Random string
Copy the code

Each example creates a different string, but they all have the same logic. Each loop creates a string by linking the results of each separate part of the calculation. We can extract the common parts and move them into individual utility functions.

function buildString(n, callback) {
  let result = ' ';
  for (let i = 0; i < n; i++) {
		result += callback(i);
	}
  return result;
}
Copy the code

The buildString function implementation contains all the common parts of each loop and uses arguments instead of variations. For example, the number of iterations of the loop is replaced by the variable N, and the construction of each string fragment is replaced by the callback function. So we can use the following higher-order functions:

const alphabet = buildString(26.(i) = > String.fromCharCode(aIndex + i);) ; alphabet;// "abcdefghijklmnopqrstuvwxyz"

const digits = buildString(10.(i) = > i);
digits;		/ / "0123456789"

Copy the code

While there are some tricky parts in the common implementation of higher-order function abstractions, such as correctly obtaining cyclic boundary conditions, they can be correctly placed in the implementation of higher-order functions.

Remember to abstract higher-order functions with clear names so that the reader can see what the code can do without diving into implementation details.

When you find yourself writing the same patterns over and over again, learning to rely on higher-order functions can make your code cleaner, more efficient, and more readable. Noticing common patterns and moving them to higher-level utility functions is an important development habit.

conclusion

  • Higher-order functions are those that take functions as arguments or return values.
  • Be familiar with higher order functions in existing libraries.
  • Learn to identify common coding patterns that can be replaced by higher-order functions.

Rule 20: Use the call method to customize the receiver to invoke the method

The receiver

Typically, the receiver of a function or method (that is, the value bound to the special keyword this) is determined by the caller’s syntax. If the method call syntax binds the object the method looks up to to the this variable, you sometimes need to use a custom receiver to call the function.

Because the function may not be an expected property of the receiver object, we can add the method as a new property to the receiver object.

obj.temp = f;
const res = obj.temp(arg1, arg2);
delete obj.temp;
Copy the code

Modifying an OBJ object is often undesirable and sometimes impossible. When using custom attributes, they may have the same name as an existing attribute in OBJ.

In addition, some objects may be frozen or sealed to prevent any attributes from being added. So it’s a bad practice to randomly add attributes to objects that aren’t common to you.

Call method

The function object has a built-in method called custom receiver. A function object may call itself through its call method.

f(arg1, arg2);
// Is equivalent to
f.call(obj, arg1, arg2)
Copy the code

The first argument to the Call method provides an explicit receiver object. The call method is also useful when the called method has been deleted, modified, or overwritten.

The hasOwnProperty method, for example, can be called by any object, even if the object is a dictionary object. In a dictionary object, looking up the hasOwnProperty property returns the value of the dictionary object’s property, not the inherited method.

dict.hasOwnProperty = 1;
dict.hasOwnProperty("foo");		// error: 1 is not a function
Copy the code

The call method using the hasOwnProperty method makes it possible to invoke methods in dictionary objects. Even if we manually delete the object.

const hasOwnProperty = {}.hasOwnProperty;
const dict.foo = 1;
delete dict.hasOwnProperty;
// Invoke a dict attribute
hasOwnProperty.call(dict, "foo");		// true
// There is no such attribute
hasOwnProperty.call(dict, "hasOwnProperty");		// false
Copy the code

Higher-order functions

An idiom for higher-order functions is to take an optional argument as the receiver of the call to the function. For example, an object representing a list of key-value pairs might provide a method called forEach.

const table = {
  entries: [].addEntry: function(key, value) {
    this.entries.push({ key, value });
  },
  forEach: function(f, thisArg) {
    const entries = this.entries;
    for (let i = 0, n = entries.length; i < n; i++) {
      constentry = entries[i]; f.call(thisArg, entry.key, entry.value, i); }}};Copy the code

The above example allows a user of a table object to pass a method f as a callback to table.forEach without providing a reasonable receiver for that method. For example, you can easily copy the contents of one table into another.

table1.forEach(table2.addEntry, table2);

This code extracts the addEntry method from Table2 (you can even extract it from Table.prototype or table1). The forEach method takes Table2 as the receiver and calls the addEntry method repeatedly.

Although the addEntry method expects only two arguments, the forEach method calls it and passes it one. Extra index parameters are simply ignored by the addEntry method.

conclusion

  • Use the call method to customize the receiver to call the function.
  • You can use the Call method to call methods that do not exist on a given object.
  • Defining higher-order functions using the call method allows the consumer to specify a receiver for the callback function.

Rule 21: Call a function with a different number of arguments using the Apply method

The apply method

In the call method, when a callback function can accept any number of arguments, it is called a variable argument or variable argument function, but the caller needs to know explicitly up front how many arguments were provided.

If we pass an array argument, we use the function object’s built-in Apply method, which is very similar to the Call method and is designed to pass array arguments with fixed elements.

The apply method specifies that the first argument is bound to the this variable of the called function. If this is not referenced in the calling function, we can simply pass null.

const scores = [1.2.3];
average.apply(null, scores);

// The above code behaves equivalent to
average.apply(scores[0], scores[1], scores[2]);
Copy the code

Variable parameter method

The apply method can also be used for variable-parameter methods. For example, the buffer object contains a mutable append method that adds elements to the state array inside the function.

const buffer = {
  state: [].append: function() {
    for (let i = 0, n = arguments.length; i < n; i++) {
      this.state.push(arguments[i]); }}}// variable argument call
buffer.append("hello, ");
buffer.append(firstName, "", lastName, "!");

Copy the code

Using the apply method’s this argument, we can also call the append method with a computable array:

buffer.append.apply(buffer, getInputStrings());

Apply the first argument, and if we pass a different object, the append method will try to modify the state property that is exclusive to the error.

conclusion

  • Use the apply method to specify a computable array of arguments to call a variadic function.
  • Use the first argument of the Apply method to provide a receiver for the variadic method.
  • Use the function’s built-in arguments local variable to call the Apply method for variadic methods.

Rule 22: Use arguments to create functions with variable arguments

arguments

A variable parameter does not define any explicit parameters. It takes advantage of JavaScript to implicitly provide a local variable object called Arguments for each function and an array-like interface for arguments. It provides an index attribute for each argument and includes a length attribute to indicate the number of arguments.

function average() {
  for (let i = 0, sum = 0, n = arguments.length; i < n; i++) {
    sum += arguments[i];
  }
  return sum / n;
}
Copy the code

Convenient fixed element method

Variadic functions provide a flexible interface that allows different callers to call them with different numbers of arguments. If a consumer wants to call a variable-parameter function with evaluated array parameters, the apply method described in article 21 can only be used.

While we can provide a convenient variable argument function, it is also better to provide a version that requires the display of the specified array of fixed elements.

function average() {
  return averageOfArray(arguments);
}
Copy the code

Write a lightweight wrapper and delegate it to a fixed tuple version to implement variadic functions. In this way, users of functions do not need to resort to the Apply method.

The Apply method reduces readability and often results in a performance penalty.

conclusion

  • Implement variadic functions using the implicit arguments object.
  • Consider providing an additional version of the fixed element for variadic functions so that users do not need to use the apply method to avoid its pitfalls.

Rule 23: Never modify arguments objects

The arguments alias

Arguments is an array of classes rather than an array. For example, when we want to call the Shift () method on the Arguments object to remove the first element of the array and move all subsequent elements one by one. Since there is no shift() method on the Arguments object, we can’t call it directly.

You can call the Arguments object by using Apply to try to extract the Shift method from the array.

function callMethod(obj, method) {
	const shift = [].shift;
  shift.call(arguments);
  shift.call(arguments);
  return obj[method].apply(obj, arguments);
}

const obj = {
  add: function(x, y) { returnx + y; }}; callMethod(obj,"add".17.25);		// error: cannot read property "apply" of undefined
Copy the code

The reason the above method fails is that the Arguments object is not a copy of the function arguments; all the named arguments are aliases for the corresponding indexes in the Arguments object. Thus, even after removing arguments from the arguments object using the shift method, obj is still the argument[0] alias, and method is still the argument[1] alias, both removed by Shift ().

So the obj[“add”] we extract is actually extracting 17[25]. Due to JavaScript’s automatic cast rules, the engine converts 17 to a Number object and extracts the 25 property, producing undefined. The last attempt to extract the apply attribute call from undefined is an error.

Strict mode

In strict mode, function arguments do not support aliasing their Arguments objects. The following example illustrates this difference by writing a function that updates an element of the Arguments object.

function strict(x) {
  "use strict";
  arguments[0] = "modified";
  console.log(arguments[0], x);
  return x === arguments[0];
}

function nonstrict(x) {
  arguments[0] = "modified";
  console.log(arguments[0], x);
  return x === arguments[0];
}

strict("unmodified");		// false
nonstrict("unmodified");	// true
Copy the code

In strict mode, arguments are not aliases for function arguments, so modified === unmodified is false. In nonstrict, modified === modified is true by alias.

Copy arguments objects

Therefore, never modify arguments objects directly. You can avoid this accident by first copying the argument heavy elements into a real array. Const args = […arguments]; .

So get the third and fourth parameters of the callMethod through the slice method and call our first example method:

function callMethod(obj, method) {
	const shift = [].shift;
  const arg = [...arguments].slice(2);
  return obj[method].apply(obj, arg);
}

const obj = {
  add: function(x, y) { returnx + y; }}; callMethod(obj,"add".17.25);		/ / 42
Copy the code

conclusion

  • Never modify arguments objects.
  • Copy the arguments object into a real array to manipulate the function arguments.

Rule 24: Use variables to save arguments references

The iterator

An iterator is a collection of data that can be accessed sequentially, with the next method fetching the next value in the sequence.

const it = values(1.4.1.4.2.1.3.5.6);
it.next();	/ / 1
it.next();	/ / 4
it.next();	/ / 1
Copy the code

We want to write a convenient function that can take any number of arguments and build an iterator for those values. Since the values function must be able to accept any number of arguments, we can construct an iterator object to iterate over the elements of the Arguments object.

function values() {
  let i = 0;
  const = arguments.length;
  return {
    hasNext: function() {
      return i < n;
    },
    next: function() {
      if (i >= n) {
        throw new Error("end of iteration");
      }
      return arguments[i++];		// wrong arguments
    };
  };
};

const it = values(1.4.1.4.2.1.3.5.6);
it.next();	// undefined
it.next();	// undefined
it.next();	// undefined
Copy the code

In this code, a new arguments variable is implicitly bound to each function body. The iterator’s next method has its arguments variable, not values, so arguments[i++] returns undefined.

Local preservation

We can simply bind a new local variable in the scope of the Arguments object we are interested in, and ensure that the nested function can only reference the display-named variable.

function values() {
  let i = 0;
  const n = arguments.length, arr = arguments;
  return {
    hasNext: function() {
      return i < n;
    },
    next: function() {
      if (i >= n) {
        throw new Error("end of iteration");
      }
      return arr[i++];		// wrong arguments
    };
  };
};

const it = values(1.4.1.4.2.1.3.5.6);
it.next();	/ / 1
it.next();	/ / 4
it.next();	/ / 1
Copy the code

conclusion

  • Beware of function nesting hierarchies when referencing arguments.
  • Bind a scoped reference to the arguments variable so that it can be referenced in nested functions.

Rule 25: Use the bind method to extract a method that has a definite receiver

Function receiver

Since methods are no different from properties whose object values are functions, it is easy to extract the object’s methods and pass the extracted functions directly to higher-order functions as callbacks. It’s easy to forget to bind the receiver of the extracted function to the object from which the function was extracted.

const buffer = {
  entries: [].add: function(s) {
    this.entries.push(s); }}const source = ["867"."-"."5309"];
source.forEach(buffer.add);		// error: entries is undefined
Copy the code

The receiver in the forEach call’s buffer.add is not a buffer object. The receiver of the function depends on how it is called, and we passed it to the forEach method. The implementation of the forEach method uses a global object as the default receiver, which has no entries property and throws undefined.

The forEach method allows the caller to provide an optional argument as the receiver of the callback function:

const buffer = {
  entries: [].add: function(s) {
    this.entries.push(s); }}const source = ["867"."-"."5309"];
source.forEach(buffer.add, buffer);
buffer.join();		/ / "867-5309"
Copy the code

According to call

If a higher-order function does not provide the consumer with a receiver for its callback function, another good solution is to create a local function that calls buffer.add with the appropriate method call syntax.

const source = ["867"."-"."5309"];
source.forEach(function(s) {
  buffer.add(s);
})
buffer.join();		/ / "867-5309"
Copy the code

Here we create a wrapper function that explicitly calls Add as a buffer object method. No matter how the wrapper function is called, it always ensures that its arguments are pushed to the receiver.

bind

It is very common to create a function that implements binding its receiver to a specified object, so the ES5 library uses the bind method for function objects. The method needs to take a receiver object and produce a wrapper function that calls the original function in the same way that the receiver object method calls it.

const source = ["867"."-"."5309"];
source.forEach(buffer.add.bind(buffer));
buffer.join();		/ / "867-5309"
Copy the code

**buffer.add.bind(buffer) creates a new function instead of modifying the buffer.add function. ** The new function behaves like the original function, but its receiver is bound to the buffer object, while the receiver of the original function remains the same.

buffer.add === buffer.add.bind(buffer); // false

So this means that it is safe to call the bind method, even for a function that might be shared elsewhere in the program, which is especially important for shared methods on prototype objects. The shared method still works when called in any descendant of the prototype.

conclusion

  • Note that extracting a method does not bind the receiver of the method to the object of the method.
  • When an object method is passed to a higher-order function, the method is invoked on the appropriate recipient using an anonymous function.
  • Use the bind method to create a function that is bound to the appropriate recipient.

Use bind to implement currification of the function

The function is currified

The technique of binding a function to a subset of its arguments is called function currification, named after logician Haskell Curry. Function corrification is a concise way to implement function delegates with fewer references than explicitly encapsulating functions.

The bind method of function objects, in addition to its purpose of binding methods to receivers, can also automatically construct anonymous functions. If you have a simple function that assembles a URL string:

function simpleURL(protocol, domain, path) {
  return protocol + ": / /" + domain + "/" + path;
}
Copy the code

A program may need to construct a site-specific path string as an absolute path URL. A natural way to do this is to use the MAP method provided by ES5 for arrays:

const urls = paths.map(function(path) {
  return simpleURL("http", siteDomain, path);
})
Copy the code

The anonymous function in the above column uses the same protocol string and website domain name string for each iteration of the Map method. The first two arguments passed to the aimpleURL function are fixed for each iteration, and only the third argument changes. We can construct this anonymous function automatically by calling the Bind method of the simpleURL function

const urls = paths.map(simpleURL.bind(null, "http", siteDomain));

The call to simpleurl. bind produces a new function that delegates to simpleURL, and the first argument to the bind method provides the value for the receiver. Since simpleURL doesn’t need to reference this, null can be used instead.

The remaining parameters to simpleurl.bind and all the parameters supplied to the new function together make up the parameters passed to simpleURL. Call simpleurl.bind with a single argument, path, and the result is a function delegated to simpleURL(” HTTP “, siteDomain, path)

conclusion

  • Use the bind method to implement currification, a delegate function that creates a fixed subset of requirements parameters.
  • The function is currified by passing null or undefined as an argument to the receiver, ignoring its receiver.

Rule 27: Use closures instead of strings to wrap code

String encapsulation

Functions are a convenient way to store code as data structures that can then be executed, and are at the heart of JavaScript’s asynchronous I/O approach. At the same time, the code can be expressed as a string and passed to the eval function to achieve the same function.

Instead, we should represent code as functions rather than strings, which is not flexible for one important reason: they are not closures. Suppose you have a simple function that repeats the user-provided action multiple times:

function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    eval(action); }}Copy the code

This function works well in the global scope, because the eval function interprets all variable references that appear in the string as global variables. If you simply move your code into a function, the variables are expected to be local, not global.

function benchmark() {
  const start = [], end = [], timings = [];
  repeat(1000."start.push(Date.now()); f(); end.push(Date.now());");
  for (let i = 0, n = start.length; i < n; i++) {
    timings[i] = end[i] - start[i];
  }
  return timings;
}
Copy the code

This function causes the repeat function to reference the global start and end variables. Something unexpected will happen, a global variable is undefined, and ReferenceError will be thrown. The code happens to be bound to the push methods of the start and end global objects, and the program becomes unpredictable.

Closure encapsulation

So we should accept closure encapsulation instead of strings.

function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    eval(action); }}function benchmark() {
  const start = [], end = [], timings = [];
  repeat(1000.function() {
   	start.push(Date.now()); 
    f(); 
    end.push(Date.now());
  });
  for (let i = 0, n = start.length; i < n; i++) {
    timings[i] = end[i] - start[i];
  }
  return timings;
}
Copy the code

Another problem with Eval is that it is often difficult for high-performance engines to optimize code in strings because the compiler does not have access to the source code as early as possible to optimize the code in time. Function expressions, on the other hand, can be compiled at the same time as their code appears, making them more suitable for standardized compilation and optimization.

conclusion

  • When passing strings to eval functions to execute their APIS, never include local variable references in the string.
  • Accepting closures is preferable to using the eval function to execute strings.

Rule 28: Do not trust the toString method of a function object

The ability of JavaScript functions to reproduce their source code as strings is occasionally used by clever hackers in clever ways:

(function(x){
  return x + 1;
}).toString();		// "function(x) {\n return x + 1; \n}"
Copy the code

However, the ECMAScript standard does not require the return result of a function object’s toString method, which means that different JavaScript engines will produce different strings, even unrelated to the function.

(function(x) {
  return x + 1;
}).bind(16).toString();		// "function(x) {\n	[native code]\n}"
Copy the code

This example failed because it used functions provided by the host environment’s built-in libraries rather than a pure JavaScript implementation. In many host environments, the bind function is implemented by other compiled languages, and the host environment provides a compiled function that does not have JavaScript source code for toString to display.

Second, because the standard allows the browser engine to change the output of the toString method, it’s easy to write programs that work correctly in one JavaScript system but not in another.

Programs are also sensitive to the specifics of a function’s source string, and even a slight change in JavaScript implementation (such as whitespace formatting) can break the code.

(function(x) {
  return function (y) {
    return x + y;
  }
})(42).toString();	// "function(y) {\n return x + y; \n}"
Copy the code

And the source code produced by the toString method does not show the values associated with internal variable references that are stored in the closure. Even though the function is actually a closure with a binding x of 42, the resulting string still contains a variable that references X.

Therefore, these limitations of the toString method make it not particularly useful or reliable for extracting function source code, and should be avoided. A fairly complex use of extracting the source code for a function should involve a well-crafted JavaScript interpreter and processing library.

Finally, it’s safest to think of JavaScript functions as abstractions that shouldn’t be violated.

conclusion

  • When calling a function’s toString method, there is no requirement that the JavaScript engine be able to retrieve the function’s source code exactly.
  • Calling the toString method on different engines may have different results.
  • The result of the toString method does not expose the local variable values stored in the closure.
  • Avoid using the toString method of function objects.

Rule 29: Avoid using non-standard stack checking properties

Stack check properties

A call stack is a chain of active functions that are currently executing, and many JavaScript environments have provided the ability to check the call stack. In some older hosting environments, each Arguments object has two additional properties: arguments.callee and arguments.caller. The former points to the function called using the Arguments object, and the latter points to the function calling the Arguments object.

Arguments.callee doesn’t have much use beyond allowing anonymous functions to recursively reference themselves.

const factorial = (function(n) {
  return (n <= 1)?1 : (n * arguments.callee(n-1));
}) 
/ / equivalent to the
function factorial(n) {
  return (n <= 1)?1 : (n * factorial(n - 1));
}
Copy the code

The arguments.caller attribute points to a function that calls a function using the arguments object. For security reasons, most environments have removed this feature. Many JavaScript environments also provide a similar function object property, caller, that points to the function’s most recent caller.

function revealCaller() {
  return revealCaller.caller;
}
function start() {
  return revealCaller();
}
start() === start;	// true
Copy the code

Stack trace

The stack trace is a problem that currently calls the data structure of the stack snapshot. It is tempting to use this property to get the stack trace.

function getCallStack() {
  const stack = [];
  for (let f = getCallStack.caller; f; f = f.caller) {
    stack.push(f);
  }
  return stack;
}
Copy the code

For simple call stacks, the getCallStack function works fine.

function f1() {
  return getCallStack();
}
function f2() {
  return f1();
}
const trace = f2();
trace;		// [f1, f2]
Copy the code

If a function appears more than once in the call stack, the stack checking logic will fall into a loop.

function f(n) {
  return n === 0 ? getCallStack() : f(n - 1);
}
const trace = f(1);	// infinite loop
Copy the code

Since function F recursively calls itself, its caller property is automatically updated to refer back to function F. So the function getCallStack gets stuck in an infinite loop looking for function F, and so the stack traces wrong.

These stack checking properties are non-standard, limited in their portability or applicability, and are prohibited in ES5’s strict functions.

function f() {
  "use strict";
  return f.caller;
}
f();	// error: caller may not be accessed on strict functions
Copy the code

The best strategy is to avoid stack checking, and if you are checking the stack for debugging purposes, it is more likely to use an interactive debugger.

conclusion

  • Avoid using non-standard arguments.caller and arguments.callee stacks to check properties, as they are not portable.
  • Avoid using nonstandard function object caller attributes, which are impossible to rely on to contain all stack information.
  • Disable the stack check property in strict mode.

After the speech

These are 18 to 29 rules for chapter 3, focusing on getting familiar with the details of JavaScript functions

The series are as follows:

  • Effective JS ’68 Rules: One to Seven
  • Effective JS’s 68 Rules 8 to 17
  • Effective JS’s 68 Rules: 18 to 29
  • Effective JS’s 68 Rules 30 to 42
  • 68 Rules of Effective JS: 43 to 52
  • Effective JS’s 68 rules: 53 to 60
  • 68 Rules of Effective JS: 61 to 68

If there is no link, you are learning…