In-depth Understanding of ES6 003 [Study Notes]

Function parameter default values, as well as 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 expressions containing the logical OR operator. When the preceding value was false, the latter value would always be returned. However, this became problematic if we passed 0 as an argument, requiring type verification. For example, `function makeRequest(url,timeout,callback){ timeout = t...`

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, and new.target binding
  • Cannot be called with the new keyword Arrow functions do not have a [[Construct]] method, so they cannot be used as constructors.
  • No prototype
  • Cannot change this binding
  • Does not support the arguments object
  • 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

(0)
Walker的头像Walker
上一篇 Mar 8, 2025 12:39
下一篇 Mar 8, 2025 10:59

Related Posts

  • Go Engineer Comprehensive Course: Protobuf Guide [Study Notes]

    Protocol Buffers Getting Started Guide 1. Introduction Protocol Buffers (protobuf for short) is a language-agnostic, platform-agnostic, extensible structured data serialization mechanism developed by Google. Compared with serialization methods such as JSON and XML, protobuf is smaller, faster, and simpler. Project homepage: https://github.com/protocolbuffers/prot…

    Personal Nov 25, 2025
    1.2K00
  • In-depth Understanding of ES6 006 [Study Notes]

    Symbol and Symbol properties The 6th primitive data type: Symbol. Private names were originally designed to allow developers to create non-string property names, but general techniques cannot detect the private names of these properties. Creating a Symbol let firstName = Symbol(); let person = {} person[firstName] = "Nicholas"; cons…

    Personal Mar 8, 2025
    1.2K00
  • TS Mount Everest 002 [Study Notes]

    Generics /* * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git &a…

    Personal Mar 27, 2025
    1.5K00
  • Go Engineer System Course 018 [Study Notes]

    Getting Started with API Gateway and Continuous Deployment (Kong & Jenkins) corresponds to the course materials "Chapter 2: Getting Started with Jenkins" and "Chapter 3: Deploying Services with Jenkins", outlining the practical path for Kong and Jenkins in enterprise-level continuous delivery. Even with zero prior experience, you can follow the steps to build your own gateway + continuous deployment pipeline. Pre-class Introduction: What is an API Gateway? An API Gateway sits between clients and backend microservices...

    Personal Nov 25, 2025
    19000
  • In-depth Understanding of ES6 008 [Study Notes]

    Iterators (Iterator) and Generators (Generator) are new features indispensable for efficient data processing. You will also find iterators present in other language features: the new for-of loop, the spread operator (...), and even asynchronous programming can use iterators. An iterator is a special object that has proprietary interfaces specifically designed for the iteration process. All iterator objects have a next() method, and each call returns a result pair...

    Personal Mar 8, 2025
    1.1K00
EN
简体中文 繁體中文 English
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹