深入理解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 002【学习笔记】

    字符串和正则表达式 字符串和正则表达式 Javascript字符串一直基于16位字符编码(UTF-16)进行构建。每16位的序列是一个编码单元(code unit),代表一个字符。length、charAt()等字符串属性和方法都基于这个编码单元构造的。Unicode的目标是为世界上每一个字符提供全球唯一的标识符。如果我们把字符长度限制在16位,码位数量将不…

    个人 2025年3月8日
    53100
  • 热爱运动,挑战极限,拥抱自然

    热爱 在这个快节奏的时代,我们被工作、生活的压力所包围,常常忽略了身体的需求。而运动,不仅仅是一种健身方式,更是一种释放自我、挑战极限、与自然共舞的生活态度。无论是滑雪、攀岩、冲浪,还是跑步、骑行、瑜伽,每一种运动都能让我们找到内心的激情,感受到生命的跃动。 运动是一场自我挑战 挑战极限,不仅仅是职业运动员的专属,而是每一个热爱运动的人都可以追求的目标。它可…

    个人 2025年2月26日
    43400
  • Go工程师体系课 protobuf_guide【学习笔记】

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

    个人 2025年4月27日
    20700
  • TS珠峰 001【学习笔记】

    课程大纲 搭建 TypeScript 开发环境。 掌握 TypeScript 的基础类型,联合类型和交叉类型。 详细类型断言的作用和用法。 掌握 TypeScript 中函数、类的类型声明方式。 掌握类型别名、接口的作用和定义。 掌握泛型的应用场景,熟练应用泛型。 灵活运用条件类型、映射类型与内置类型。 创建和使用自定义类型。 理解命名空间、模块的概念已经使…

    个人 2025年3月27日
    38100
  • 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日
    39800

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

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

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