Functions
Default parameter values, and some details about the arguments object, how to use expressions as parameters, and the temporal dead zone for parameters.
Previously, setting default values always relied on the principle thatin an expression containing a logical OR operator, if the first value is false, the second value is always returned. However, if we pass 0 as a parameter, it becomes troublesome. We need to verify the type.
function makeRequest(url,timeout,callback){
timeout = timeout || 2000;
callback = callback || function(){};
// 逻辑
}
// 解决传0问题
function makeRequest(url,timeout,callback){
timeout = typeof timeout !=='undefined'?timeout: 2000;
callback = typeof callback !=='undefined'?callback : function(){};
// 逻辑
}
Default Parameters
Not passing a value or passing undefined. If null is passed, the default will not be used; it will be considered a valid value. Impact of default parameters on the arguments object. Previously, parameter passing would be reflected in the arguments object. In ES5 non-strict mode, changes to named parameters would be synchronously updated in the arguments object, but in ES5 strict mode, this behavior was removed, and the arguments object no longer changes with parameter variations. See below.
function mixArgs(first,second){
"use strict"
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = 'c';
second = 'd'
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}
mixArgs('a','b')
// true
// true
// false
// false
In ES6, if a function uses default parameter values, the behavior of the arguments object will be consistent with ES5 strict mode, regardless of whether strict mode is explicitly defined.
Default Parameter Expressions
The `getValue()` method is not called when the function declaration is first parsed; it is only called when the `add()` function is invoked without passing a second argument.
let value = 5;
function getValue(){
return value++;
}
function add(first,second=getValue()){
return first+second;
})
console.log(add(1,1)) // 2
console.log(add(1)) // 6
console.log(add(1)) //7
Parameters defined earlier can be used as default values for parameters defined later; the reverse is not true (e.g., `first=second, second`) because `second` is defined later than `first`, so it cannot be `first`'s default value. To help you understand the underlying principle, let's revisit the concept of the Temporal Dead Zone (TDZ). Similar to `let` declarations, when parameters are defined, a new identifier binding is created for each parameter, and this binding cannot be referenced before initialization; attempting to access it will cause the program to throw an error.
Function parameters have their own scope and temporal dead zone, which are independent of the function body's scope. This means that default parameter values cannot access variables within the function body.
Regardless of whether rest parameters are used, the `arguments` object always contains all parameters passed to the function.
Handling Unnamed Parameters -- Rest Parameters
// 函数的length属性统计的是函数命名参数的数量,不定参数的加入不会影响length属性的值。
function pick(object, ...keys){
let result = Object.create(null);
for(let i=0,len=keys.length;i<len;i++){
result[keys[i]] = object[keys[i]]
}
return result;
}
- Rest parameters (used in function parameters, of course)
- Each function can declare at most one rest parameter
- And it must be placed at the end of all parameters
- Rest parameters are not allowed in setters because an object literal setter can only have one parameter (in the definition of rest parameters, the number of parameters can be infinite, so rest parameters are not allowed in this context).
let object = {
// 语法错误 不可以在Setter中使用不定参数
set name(...value){
// ...
}
}
Regardless of whether rest parameters are used, the `arguments` object always contains all parameters passed to the function.
Enhanced Function Constructor
Default values and rest parameters can be used when dynamically creating functions. The only thing needed is to add an equals sign and a default value after the parameter name, and to define rest parameters, simply add ... before the last parameter. See below.
var add = new Function('first','second=first','return first+second')
console.log(add(1,1)) // 2
console.log(add(1)) // 2
var pickFirst = new Function("...args",'return args[0]')
console.log(pickFirst(1,2)) // 1
Spread Operator
The spread operator allows you to specify an array and then break its elements apart to be passed as individual arguments to a function. JavaScript's built-in Math.max() method can accept any number of arguments and return the largest one; this is a simple use case.
My understanding: Note that the former is used during definition, while the latter is used during invocation (parameters are separate, and you have an array).
Below, the previous way to find the maximum value in an array.
let values = [25,50,75,100]
console.log(Math.max.apply(Math,values)) // 100;
// max方法不允许传入一个数组
console.log(Math.max(...values))
// 展开运算符后面可以继续添加参数
console.log(Math.max(...values,110)) //110
Function's name Property
For a declared function, the .name property is its declared function name. For a function expression, if the function is anonymous, it's the variable's name; otherwise, it's its declared name.
It is merely additional information for debugging purposes, so the value of the `name` property cannot be used to obtain a reference to the function.
function somethingDoing(){
// 空函数
}
var doSomeThing = function doSomethingElse() {
// 空函数
}
var person = {
get firstName(){
return 'Nicholas';
},
sayName:function(){
console.log(this.name)
}
}
console.log(somethingDoing.name) // somethingDoing
console.log(doSomeThing.name) // doSomethingElse 函数表达式有个名字这个名字比要赋值的变量权重高
console.log(person.sayName.name) // sayName
console.log(person.firstName.name) // get firstName
// 两个特例
// 通过bind()创建的函数 会带前缀bind
var doSomthing = function(){}
console.log(doSomthing.bind().name) // "bound doSomething"
console.log(new Function().name) // anonymous
Clarifying Multiple Uses of Functions
In ES5, when new is used, the this value inside the function points to a new object, and the function ultimately returns a new object (if no object is explicitly returned).
function Person(name){
this.name = name;
}
var person = new Person('Nicholas');
var notPerson = Person('Nicholas');
console.log(person) // '[Object object]'
console.log(notPerson) // undefined
In the example above, in ES6, functions have two different internal access methods: [[Call]] and [[Construct]]. When a function is called with the new keyword, the [[Construct]] method is executed. It is responsible for creating a new object, typically called an instance, and then executing the function body, binding this to the instance. When not using the new keyword, the [[Call]] method is invoked to execute the function body. Functions that have the [[Construct]] method are collectively called constructors. Remember that not all functions have the [[Construct]] method, such as arrow functions.
ES5: Determining if a function was called with `new`.
function Person(name){
if(this instanceof Person){
this.name = name
}else {
throw new Error('必须通过new关键字来调用Person')
}
}
The method above can avoid it to some extent, but don't forget about call and apply...
var person = new Person('Nicholas');
var person = Person('Nicholas'); // 报错
var person = Person.call(person,'Michael'); // 通过
The new.target Meta-Property
To solve the problem of determining whether a function was called with the new keyword, ES6 introduced the new.target meta-property. A meta-property is a non-object property that provides supplementary information about a non-object target. When a function's [[Construct]] method is called, new.target is assigned the new operation's target, which is typically the newly created object instance, i.e., the constructor of this within the function body. If the [[Call]] method is invoked, new.target's value is undefined. With this meta-property, you can safely detect whether a function was called with the new keyword by checking if new.target is defined.
function Person(name){
if(typeof new.target!=='undefined'){
this.name = name;
}else {
throw new Error('必须通过new关键字来调用Person')
}
}
var person = new Person('Nicholas'); // 通过
var person = Person('Nicholas'); // 报错
var person = Person.call(person,'Michael'); // 报错
It's also possible to check if new.target was called by a specific constructor. See below:
function Person(name){
// if(typeof new.target!=='undefined'){
if(new.target===Person)
this.name = name;
}else {
throw new Error('必须通过new关键字来调用Person')
}
}
function AnotherPerson(name){
Person.call(this,name)
}
var person = new Person('Nicholas');
var otherPerson = new AnotherPerson('Michael') // 抛出错误
Block-Level Functions
In ES5 strict mode, declaring functions within code blocks is not allowed. In ES6, the doSomething() function is treated as a block-level declaration, allowing it to be accessed and called within the code where it is defined:
"use strict"
if(true){
console.log(typeof doSomething) // "function"
function doSomething(){
// 空函数
}
doSomething() //
}
typeof doSomething // undefined
It's similar to using let, the only difference being that block-level functions hoist their declarations to the top of the code block. Note the difference from function expression declarations. In ES6, even in non-strict mode, block-level functions can be declared, but their behavior differs slightly from strict mode. These functions are no longer hoisted to the top of the code block but rather to the top of the enclosing function or global scope, as shown below:
// ECMAScript 6中的行为
if(true){
console.log(typeof doSomething) // "function"
function doSomething(){
// 空函数
}
doSomething() //
}
typeof doSomething // function
Arrow Functions
let PageHandler = {
id:"123456",
init: function(){
document.addEventListener("click",function(event){
this.doSomething(event.type); // 抛出错误
},false)
},
doSomething: function(type){
console.log("Handling "+ type + " for "+this.id)
}
}
The code above did not run as expected. This is because this is bound to the event target object (here, document), not to PageHandler. And since this.doSomething() does not exist in the target document, it cannot execute normally. The previous approach to fix this issue was to explicitly bind this to the PageHandler function using the bind() method. See below.
let PageHandler = {
id:"123456",
init: function(){
document.addEventListener("click",(function(event){
this.doSomething(event.type);
}).bind(this),false)
},
doSomething: function(type){
console.log("Handling "+ type + " for "+this.id)
}
}
Calling bind(this) effectively creates a new function whose this is bound to the current this, which is PageHandler. To avoid creating an extra function, we can fix this code in a better way: using arrow functions. Arrow functions do not have their own this binding; its value must be determined by looking up the scope chain.
let PageHandler = {
id:"123456",
init: function(){
document.addEventListener("click",(event)=>{
this.doSomething(event.type);
},false)
},
doSomething: function(type){
console.log("Handling "+ type + " for "+this.id)
}
}
- No
this,super,arguments, andnew.targetbinding - Cannot be called with the
newkeyword Arrow functions do not have a[[Construct]]method, so they cannot be used as constructors. - No prototype
- Cannot change
thisbinding - Does not support the
argumentsobject - Does not support duplicate named parameters
Arrow functions also have a name property, which follows the same rules as other functions.
this Binding
If an arrow function is contained within a non-arrow function, this is bound to the this of the nearest enclosing non-arrow function; otherwise, the value of this will be set to undefined.
Arrow Functions and Arrays
Arrow function syntax is concise and very suitable for array processing. For example, to sort an array, it's typically done as follows:
var result = values.sort(function(a,b){
return a - b;
})
// We can shorten it as follows
var result = values.sort((a,b)=>a-b)
Array methods that accept callback functions, such as
sort(),map(),reduce(),
Tail Call Optimization
A function is called as the last statement of another function.
function doSomething(){
return doSomethingElse() // 尾调用;
}
In ES5 engines, tail calls are implemented similarly to other function calls: a new stack frame is created and pushed onto the call stack to represent the function call. This means that in recursive calls, every unfinished stack frame is kept in memory, which can lead to program issues when the call stack becomes too large.
To reduce the size of the tail call stack in strict mode (non-strict mode is unaffected), if the following conditions are met, tail calls no longer create new stack frames but instead clear and reuse the current stack frame:
- The tail call does not access variables in the current stack frame (meaning the function is not a closure).
- Within the function, the tail call is the last statement.
- The result of the tail call is returned as the function's value.
"use strict"
function doSomething(){
return doSomethingElse() // 尾调用优化;
}
// 注意以上3个条件
How to Leverage Tail Call Optimization
function factorial(n){
if(n<=1){
return 1
} else {
// 无法优化
return n * factorial(n-1)
}
}
// 可以利用传递第二个参数来保存每次阶乘的结果(并且默认结果是1)
function factorial(n,p=1){
if(n<=1){
return 1*p
}else {
let result = n*p;
//优化后
return factorial(n-1,result)
}
}
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/4310