深入理解ES6 003【学习笔记】

函数

参数默认值,以及一些关于arguments对象,如何使用表达式作为参数、参数的临时死区。

以前设置默认值总是利用在含有逻辑或操作符的表达式中,前一个值是false时,总是返回后面一个的值,但如果我们给参数传0时,就有些麻烦。需要去验证一下类型

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(){};
  // 逻辑
}

默认参数

不传或传undefined。如果传null不会用默认,会认为是一个合法的值。默认参数对arguments对象的影响。之前的参数传递会体现在arguments对象上。 在es5非严格模式下,命名参数的变化会同步更新到arguments对象上,但在es5的严格模式下,取消了这个行为,无论参数如何变化,arguments对象不再随之改变。 如下

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

es6中,如果一个函数使用了默认参数值,则无论是否显式定义了严格模式,arguments对象的行为都将与es5 严格模式下保持一致。

默认参数表达式

初次解析函数声明时不会调用getValue()方法,只有当调用add()函数且不传入第二个参数时才会调用。

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

先定义参数可以作为后定义参数的默认值,反过来不成立(first=second,second)因为second比first晚定义,因此其不能作为first的默认值。为了帮助你理解背后的原理,我们重温一下临时死区TDZ的概念。与let声明类似,定义参数时会为每个参数创建一个新的标识符绑定,该绑定在初始化之前不可被引用,如果试图访问会导致程序抛出错误。
函数参数有自己的作用域和临时死区,其与函数体的作用域是各自独立的,也就是说参数的默认值不可访问函数体内的变量
无论是是否使用不定参数,arguments对象总是包含所有传入函数的参数

处理无命名参数--不定参数

// 函数的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;
}
  • 不定参数(当然在函数参数中使用)
  • 每个函数最多只能声明一个不定参数
  • 而且一定要放在所有参数的未尾
  • 不许在setter中使用不定参数,是因为对象字面量setter的参数有且只能有一个(在不定参数的定义中,参数的数量可以无限多,所以在当前上下文中允许使用不定参数。
let object = {
  // 语法错误 不可以在Setter中使用不定参数
  set name(...value){
    // ...
  }
}

无论是否使用不定参数,arguments对象总是包含所有传入函数的参数

增强的Function构造函数

可以在动态创建函数时使用默认值及不定参数,唯一需要做的是在参数名后添加一个等号和一个默认值,定义不定参数,只需在最后一个参数前添加...。如下

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

展开运算符

展开运算符可以让你指定一个数组,将它们打散后作为各自独立的参数传入函数。JavaScript内建的Math.max()方法可以接受任意数量的参数并返回值最大的那个,这是一个简单的用例

自己理解,注意,前面那个是定义时用的,这个是调用时使用(参数是分开的,而你有一个数组)

如下,之前数组求最大值的做法

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

函数的name属性

声明函数.name属性是它声明函数名,函数表达式如果函数是匿名的则就是其变量的名称,如果不是则是其声明的名字,

它只是协助调试用的额外信息,所以不能使用name属性的值来获取对于函数的引用。

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

明确函数的多重用途

es5 当new 时 函数内的this值指向一个新对象,函数最终返回一个新对象(没有明确返回一个对象时)

function Person(name){
  this.name = name;
}
var person = new Person('Nicholas');
var notPerson = Person('Nicholas'); 
console.log(person) // '[Object object]'
console.log(notPerson) // undefined

上例中,在es6中函数有两个不同的内部访问[[Call]][[Construct]]。当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后执行函数体,将this绑到实例上。而不使用new关键字时,则调用[[Call]]执行函数体,具有[[Construct]]方法的函数统称为构造函数。切记不是所有的函数都有[[Construct]]方法 如:箭头函数

es5判断函数是否被new调用

function Person(name){
  if(this instanceof Person){
    this.name = name
  }else {
    throw new Error('必须通过new关键字来调用Person')
  }
}

上面的方式在一定程度上可以避免,但不要忘了还有一个call和apply呢……

var person = new Person('Nicholas');
var person =  Person('Nicholas'); // 报错
var person =  Person.call(person,'Michael'); // 通过

无属性new.target

为了解决判断函数是否通过new关键字调用的问题,es6引入了new.target这个元属性,元属性是指非对象的属性,其可以提供非对象目标的补充信息。当调用函数[[Construct]]方法时,new.target上被赋为new的操作目标,通常是新创建对象实例,也就是函数体内this的构造函数;如果调用[[Call]]方法,则new.target值为undefined,有了这个元属性,可以通过检查new.target是否被定义过来安全地检测是否通过new关键字调用的。

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'); // 报错

也可能通过new.target是否被某个特定的构造函数所调用。如下:

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') // 抛出错误

块级函数

es5的严格格式下,我们在代码块中声明函数是不被允许的,而在es6中将doSomething()函数视作一个块级声明,从而可以在定义该函数的代码内访问和调用它:

"use strict"

if(true){
  console.log(typeof doSomething) // "function"
  function doSomething(){
    // 空函数
  }
  doSomething() //
}
typeof doSomething // undefined

和let的使用差不多,唯一区别的是块级是函数,会将声明提到代码块的顶部。注意函数表达式声明的区别。在es6中,即使处于非严格模式下,也可以声明块级函数,但其行为与严格模式下稍有不同。这些函数不再提升至代码块的顶部,而是提升至外围函数或全局作用域的顶部,如下:

// ECMAScript 6中的行为

if(true){
  console.log(typeof doSomething) // "function"
  function doSomething(){
    // 空函数
  }
  doSomething() //
}
typeof doSomething // function

箭头函数

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)
  }
}

上面的代码并没有如预期的正常运行。因为this的绑定是事件目标对象的引用(这里是document),而没有绑定在PageHandler,且由于this.doSomething()在目标document中不存在,所以无法正常执行,之前的做法是通过bind()方法显示地将this绑定到PageHandler函数上来修正这个问题。如下

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)
  }
}

调用bind(this)后事实上创建了一个新函数,它的this被绑定到当前的this即PageHandler。为了避免创建一个额外的函数,我们可以通过一个更好的方式来修正这段代码,使用箭头函数。箭头函数中没有this绑定,必须通过查找作用域链来决定其值。

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)
  }
}
  • 没有this、super、arguments和new.target绑定
  • 不能通过new关键字调用 箭头函数没有[[Construct]]方法,所以不能被用作构造函数
  • 没有原型
  • 不可以改变this的绑定
  • 不支持arguments对象
  • 不支持重复的命名参数

箭头函数也同样有一个name属性,这与其它函数的规则相同

this绑定

如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this;否则,this的值会被设置为undefined.

箭头函数和数组

箭头函数的语法简洁,非常适用于数组处理。举例来说,比如给数组排序,通过是

var result = values.sort(function(a,b){
  return a - b;
})

// 我们可以将其简写至如下
var result = values.sort((a,b)=>a-b)

诸如sort()map()reduce()这些可以接受回调函数的数组方法,

尾调用优化

函数做为另一个函数的最后一条语句被调用。

function doSomething(){
  return doSomethingElse() // 尾调用;
}

在es5引擎中,尾调用的实现与其他函数调用的实现类似:创建一个新的栈帧(stack frame),将其推入调用栈来表示函数调用。也就是说,在循环调用中,每一个未用完的栈桢都会被保存在内存中,当调用栈变得过大时会造成程序问题。

为了缩减严格模式下尾调用栈的大小(非严格格式不受影响),如果满足以下条件,尾调用不再创建新的栈桢,而是清除非重用当前栈桢

  • 尾调用不访问当前栈桢的变量(也就是说函数不是一个闭包)
  • 在函数内部,尾调用是最后一条语句
  • 尾调用的结果作为函数的返回值
"use strict"

function doSomething(){
  return doSomethingElse() // 尾调用优化;
}

// 注意以上3个条件

如何利用尾调用优化


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://joyjs.cn/archives/4310

(0)
Walker的头像Walker
上一篇 2025年3月8日 12:39
下一篇 2025年3月8日 10:59

相关推荐

  • 深入理解ES6 001【学习笔记】

    块级作用域绑定 之前的变量声明var无论在哪里声明的都被认为是作域顶部声明的,由于函数是一等公民,所以顺序一般是function 函数名()、var 变量。 块级声明 块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域存在于: 函数内部 块中(字符和{和}之间的区域) 临时死区 javascript引擎在扫描代码发现变量声明时,要么将它们提升至作…

    个人 2025年3月8日
    1.4K00
  • TS珠峰 003【学习笔记】

    装饰器 // 装饰器 // 只能在类中使用(类本身,类成员使用) // 装饰器,类的装饰器,属性装饰器,访问装饰器 参数装饰器 // 1. 类型装饰器 给类进行扩展,也可以返回一个子类 // 先要在tsconfig.json中开启experimentalDecorators const classDecorator1 = <T extends new …

    个人 2025年3月27日
    1.2K00
  • Go工程师体系课 protobuf_guide【学习笔记】

    Protocol Buffers 入门指南 1. 简介 Protocol Buffers(简称 protobuf)是 Google 开发的一种语言无关、平台无关、可扩展的结构化数据序列化机制。与 JSON、XML 等序列化方式相比,protobuf 更小、更快、更简单。 项目主页:https://github.com/protocolbuffers/prot…

    个人 2025年4月27日
    1.0K00
  • 深入理解ES6 011【学习笔记】

    Promise与异步编程 因为执行引擎是单线程的,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列中,每当一段代码准备执行时,都会被添加到任务队列中,每当引擎中的一段代码结束执行,事件循环会执行队列中的一下个任务。 Promise相当于异步操作结果占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise,就像这样…

    个人 2025年3月8日
    92300
  • TS珠峰 002【学习笔记】

    泛型 /* * @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…

    个人 2025年3月27日
    1.3K00

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹