# JavaScript进阶系列之function篇

in 网站建设
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

JavaScript进阶系列之function篇

每天都在codeing,但是如果没有总结的话,根本记不住。以后定期写文章,不管有没有人看都会有一定的收获。

目录:

我的GitHub,欢迎star

函数的参数

默认参数

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

function fn(a, b = 1) {

}
fn(1)

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

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

形参列表里的参数的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) {

}

使用限制:

一些注意的点:

其实在ES4的草案中,arguments对象是会被不定参数给干掉的,不过ES4搁置以后,等到ES6出来,它很ES4的区别是保留了arguments对象

arguments

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

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和参数则不会保持同步。

箭头函数

与普通函数的区别:

注意,能否被用作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,然后重用即可。这样,尾递归的时候,整个调用栈的大小就不会变了,达到了优化的效果。

以下情况会优化:

function fn1() {
  // 其他语句
  return fn2()
}

深入点不知道的

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

具有[[construct]]内部方法的函数被统称为构造函数。不是所有的函数都是构造函数,所以不是所有的函数都能够被new调用(比如箭头函数)。这个具体细节看下文。

JS中的三种Function

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

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

而即便都是new调用,built in 和 普通的 function object还是有所差别:

经常看到面试题问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

总结

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

延伸

一个function object可以用new调用的条件是什么?

也就是说,是否可以用new方式调用,和函数是不是构造函数没有关系,有没有prototype也没关系,只要函数对象上具有内部的[[construct]],并且函数本身是允许new调用的,就可以通过new来调用该function

一些值得注意的点

if (true) {
  function a() {}
}
console.log(a) // undefined
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.

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

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看