# JavaScript进阶系列之function篇

JavaScript进阶系列之function篇

天天都在codeing,可是若是没有总结的话,根本记不住。之后按期写文章,无论有没有人看都会有必定的收获。git

目录:

个人GitHub,欢迎stargithub

函数的参数

默认参数

使用了默认参数的函数,会自动启用ES6面试

function fn(a, b = 1) {

}
fn(1)

不传或者手动传递undefined都会使用默认的参数。闭包

除此以外,和正常的ES5还有一些区别:app

  • 形参列表里的参数,都至关于使用let声明的同样(意味着存在TDZ)
  • 默认参数能够是形参列表里的变量,但不能够是函数体内的变量哦。
  • 使用了默认参数,说明当前使用的ES6,因此当前scope都是处于strict模式下的。
形参列表里的参数的 scope和函数体内的 scope是两个 scope(书上是这么说的,可是他妈的若是是两个 scope,那我从新用 let声明为何还报错?干!因此我以为应该只是说形参列表的默认参数不能使用函数做用域内部的变量,但仍是属于 同一个scope,由于相似的 for循环,括号里和花括号里是两个 scope我就能用 let重复声明)

默认参数对arguments的影响

记住一点,严格模式下arguments只和传入的实参同样,并且不保证同步。函数

因此一旦使用了默认参数,就说明要么是没传,要么是传了undefined。那arguments里就确定没有默认参数了。优化

function fn(a, b = 1) {
  console.log(arguments[0] === a) // true
  console.log(arguments[1] === b) // false
}
fn(1)

无名参数

function fn(a, ...args) {

}

使用限制:ui

  • 不定参数只能在参数列表的最后
  • 不定参数不能用在setter中,由于setter的参数只能有一个value,在不定参数的定义中是能够有无限多。这二者在当前上下文中不容许

一些注意的点:this

  • arguments中只存储传入的参数
  • fn.length则是命名参数的个数,也就是说fnlength属性是不包括args中的东东的
其实在 ES4的草案中, arguments对象是会被不定参数给干掉的,不过 ES4搁置之后,等到 ES6出来,它很 ES4的区别是保留了 arguments对象

arguments

在非严格模式下,arguments对象和实参保持同步:prototype

function fn(a, b) {
  console.log(a === arguments[0])
  a = 'hehe'
  console.log(a === arguments[1])
}
fn(1, 2)
结果都是true

之因此给实参加粗,是由于即便保持同步,也只是和传入的参数保持一致,好比我若是没有传入b,而后我修改了b,这个时候arguments[0]b是不一致的。

可是在严格模式下,arguments和参数则不会保持同步。

箭头函数

与普通函数的区别:

  • 没有new.target、this、arguments、super,这些东西都是最近一层非箭头函数的东西.(因此,一旦不存在这样的函数,可是在箭头函数中访问了这些keyword就会抛出错误)
  • 不能被new调用,没有[[construct]]内部方法
  • 没有prototype属性
  • this遵循词法做用域,运行过程当中不会改变
  • 不管是否为严格模式,都不能有同名的参数
  • 由于没有arguments,因此参数只能经过命名参数和不定参数来访问
  • 不能使用yield关键字,因此也就不能当作generator函数咯
注意,可否被用做 constructor和其有无 prototype属性无关

就算用call、apply、bind这样的方法,也无法改变箭头函数的this。不过经过bind能够传递参数却是真的

函数的name属性

name属性是为了更好地辨别函数:

function fn() {}  // fn
const a = function() {} // a
const b = fn // fn
const c = a // a
const d = function hehe() {} // hehe

注释就是对应函数的name。仔细观察很容易发现,若是函数是使用函数声明建立的,那name就是function关键字后的string。若是是使用赋值语句建立的,那name就是对应的变量名。并且一旦function.name肯定下来,后续赋值给其余变量也不会改变。其中function声明比赋值语句的优先级高。

特殊状况:

const obj = {
  get name() {

  },
  hehe() {

  }
}
console.log(obj.name) // 书上说是 get name,可是我亲测是undefined啊
console.log(obj.hehe) // hehe

另外bind出来的函数,name带有bound前缀;经过Function建立的函数带有anonymous

函数的 name属性不必定同步于引用变量,只是一个协助调试用的额外信息而已,因此不要使用 name属性来获取函数的引用

函数节流、函数防抖

节流就是等到你不触发了我在执行:

function debounce(fn, time, immediate = false) {
  let clear
  return function(...args) {
    if (immediate) {
      immediate = false
      fn(...args)
      return
    }
    if (clear) {
      clearTimeout(clear)
    }
    clear = setTimeout(() => {
      fn(...args)
      clear = 0
    }, time)
  }
}

防抖就是不管你触发多少次,我只在规定的时间里触发一次

function throttle(fn, time, immediate = false) {
  let clear
  let prev = 0
  return function(...args) {
    if (immediate) {
      immediate = false
      fn(...args)
      return
    }
    if (!clear && Date.now() - prev >= time) {
      prev = Date.now()
      clear = setTimeout(() => {
        fn(...args)
        clear = 0
        prev = 0
      }, time)
    }
  }
}

尾递归

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

ES5中,尾调用的实现和普通的函数调用同样,都是建立一个新的stack frame,将其push到调用栈,来表示函数调用,若是在循环调用中,调用栈的大小过大就会爆栈。

而尾递归优化呢,指的就是不在建立新的stack frame,而是清除掉当前的stack frame,而后重用便可。这样,尾递归的时候,整个调用栈的大小就不会变了,达到了优化的效果。

如下状况会优化:

  • 尾调用不访问当前stack frame的变量。也就是说函数不能是一个闭包
  • 在函数内部,必须是最后一条语句
  • 尾调用的结果做为返回值返回
function fn1() {
  // 其余语句
  return fn2()
}

深刻点不知道的

JavaScript函数中有两个内部方法:[[call]][[construct]]。经过new来调用函数的时候执行的是construct内部方法,而正常调用则执行call内部方法。

  • 前者负责建立一个实例instance,而后执行函数体。当使用new调用函数的时候,new.target被赋值为new操做符的目标,一般就是被new调用的构造函数。因此若是须要判断函数是否被new调用,则只须要查看new.target是否为undefined便可
  • 后者直接执行代码中的函数体
具备 [[construct]]内部方法的函数被统称为构造函数。不是全部的函数都是构造函数,因此不是全部的函数都可以被 new调用(好比箭头函数)。这个具体细节看下文。

JS中的三种Function

JS目前具备三种类型的function object

  • ECMAScript Function Object:全部经过JS语言生成的function object都是ECMAScript Function Object
  • Built-in Function:引擎内置的全部function object若是没有实现为ECMAScript Function Object,必须实现为此处的Built-in Function Object
  • Bound FunctionFunction.prototype.bind生成的function object为<u>Bound Function Object</u>,调用<u>Bound Function Object</u>会致使调用绑定的<u>bound target function</u>;

ES6标准指出,函数内部都有两个方法:[[call]] [[construct]] 。前者是普通调用,后者是new调用。

而即使都是new调用,built in 和 普通的 function object仍是有所差异:

  • new operator做用于ECMAScript Function Object会根据当前function objectprototype属性生成一个新的对象,并将其做为this传入function object进行调用;
  • new operator做用于Built-in Function Object的时候不会生成一个新的对象做为this传入当前的function object,而是由当前的function objectfunction call的时候本身生成一个新的对象。
常常看到面试题问 new operator执行了哪些操做,而后就开始巴拉巴拉:根据原型生成一个新的对象,而后将新的对象做为this调用函数,最后根据函数的返回值是否为对象来判断应该返回什么。。。(心中千万只草泥马飘过);固然,若是要用 JS来模拟 new operator那只能按照这个流程搞,顶多再用上 new.target

js中的函数都有prototype?

之前一直觉得全部js函数都有prototype,直到最近才发现不是。

除非在特定函数的描述中另有指定,不然不是构造函数的内置函数不具备原型属性。

也就是说,js的一些内置函数原本就没打算用做constructor,也就没有添加[[construct]] internal-method。可是反过来不必定成立,由于有的构造函数没有prototype,但它仍然是一个构造函数,好比:

console.log(Proxy.prototype); // undefined
// 可是能够经过new Proxy(args)来建立对象

按照规范,若是一个function-object 既具备prototype属性,又具备[[construct]] internal-method,那么它就是一个constructor,此时该function-object承担着creates and initializes objects的责任;

Proxy constructor为何没有prototype属性呢?虽然constructor用于 creates and initializes objects,但若是生成的对象的[[prototype]]属性不须要constructorprototype属性初始化,那么constructorprototype就没有存在的必要。

也就是说,大部分状况下只要某个 functionprototype属性,同时又具备 [[constructor]],那这个 function就是一个 constructor

可是某些特殊状况下也会有例外,即:它不承担建立对象而且初始化。可是因为某些缘由它又同时具有了上述条件。

这是规范中指出的,目前尚未在 built-in function中发现过这种特例。不过在 function object中有两个特例。

generator function

generator 不是 constructor ,可是同时具有 prototype

Function.prototype.bind生成的Bound function object

经过 bind 生成的bound function 是没有 prototype 属性,不过它仍然能够看成一个 constructor

总结

综上所述,明确了如下几点:

  • 不是全部函数都是构造函数,必须有内部方法[[construct]]
  • 不是全部函数都有prototype属性
  • 有无prototype属性和函数是否为构造函数无关,只要有[[construct]]属性就是构造函数
  • 不是有所的构造函数都能被new调用,好比Symbol

延伸

一个function object能够用new调用的条件是什么?

也就是说,是否能够用new方式调用,和函数是否是构造函数没有关系,有没有prototype也不要紧,只要函数对象上具备内部的[[construct]],而且函数自己是容许new调用的,就能够经过new来调用该function

一些值得注意的点

  • 严格模式下,function声明是存在块级做用域的。不过在当前的scope中不存在TDZ。非严格模式下则不存在块级做用域的特性,会直接提高至顶层做用域
if (true) {
  function a() {}
}
console.log(a) // undefined
  • 方法和函数:在ES6以前js中的方法就是某个函数做为某个对象的非数据属性,除此以外和函数没有任何区别。可是在ES6以后,全部的方法内部都有一个[[HomeObject]]属性,对象的方法有,可是普通的函数则没有。通常状况下都不会有什么区别,可是在使用super的时候会有区别
const proto = {
  method() {
    return 'this is a method on proto'
  }
}

const obj = Object.setPrototypeOf({
  test() {
    console.log(super.method())
  }
}, proto)
obj.test() // this is a method on proto

const obj = Object.setPrototypeOf({}, proto)
obj.test = function() {
  super.method() // 语法错误
}
obj.test()

最后

个人GitHub,欢迎star.

发现错误,欢迎在评论里指出😆。

相关文章
相关标签/搜索