函数是指将代码分割成可复用的片段

  1. <br />一般来说,一个函数是可以通过外部代码调用的一个“子程序”(或在递归的情况下由内部函数调用)。像程序本身一样,一个函数由称为函数体的一系列语句组成。值可以传递给一个函数,函数将返回一个值。<br />在 JavaScript中,函数是头等(first-class)对象,因为它们可以像任何其他对象一样具有属性和方法。它们与其他对象的区别在于函数可以被调用。简而言之,它们是[Function](https://developer.mozilla.org/zh-cn/JavaScript/Reference/Global_Objects/Function)对象。<br />也可以叫函数是action object<br />如果函数挂载在对象上,就称它为对象的方法<br />函数是“可调用对象”,它有一个内部属

性 [[Call]],该属性使其可以被调用。
函数有内部属性[[Construct]],表明该函数可以作为构造函数,箭头函数没有这个属性,说明不能作为构造函数

函数种类

普通函数,由 function关键字定义
箭头函数
generator 函数
异步函数

内部属性

[[call]]

如果该对象有 call 内部属性,说明可以调用是函数对象,typeof 返回 function 是因为根据 call

[[construct]]

如果有 construct 内部属性的函数可以作为构造函数被 new 调用,只有普通函数和 class 定义的类有 constuct

[[scopes]]

  1. var a = 1;
  2. function fun(){
  3. var b = 1;
  4. const p = function p () {
  5. console.log(b)
  6. console.dir(p)
  7. }
  8. p();
  9. }
  10. fun();

Screen Shot 2020-05-06 at 2.52.14 PM.png
https://segmentfault.com/a/1190000012842459

[[FunctionLocation]]

类似于debugger功能,可以很容易的查找到此函数的代码位置,

描述

  • 在JavaScript中,每个普通函数其实都是一个Function对象。

    1. (function(){}).constructor === Function // true
    2. (() => {}).constructor === Function // true
    3. (class {}).constructor === Function // true
    4. (async function (){}).constructor === Function // false
    5. (function *(){}).constructor === Function // false
    6. (async function *(){}).constructor === Function // false
  • 如果一个函数中没有使用return语句,则它默认返回undefined。

    函数定义

    函数声明语句

  • 函数名是变量名,指向函数对象

  • 一对圆括号里面指定参数
  • 一堆花括号里面是函数体,调用函数的时候执行
  • this指向函数的调用上下文,
  • 函数是动态作用域,在运行时绑定
  • 函数声明语句是函数提升,声明和函数体都会提前,重名还会被覆盖
  1. function foo() {
  2. console.log('foo1');
  3. }
  4. foo(); // foo2
  5. function foo() {
  6. console.log('foo2');
  7. }
  8. foo(); // foo2
  9. // 预编译 定义 foo,重名第二个覆盖第一个
  10. gc = {
  11. vo: {
  12. foo: reference -> function foo() { console.log('foo2');}
  13. }
  14. }
  15. // 执行的时候略过预编译声明的函数

函数表达式

  • 函数名可选,指代函数对象本身,没有函数名的叫匿名函数,当函数只使用一次用 IIFE
  • 函数定义表达式是变量提升,跟其他用 var 定义的变量一样,只是把变量的声明提前了,变量的初始化代码还在原来的位置
  1. //匿名函数表达式
  2. var foo = function (){}
  3. //命名函数表达式
  4. var foo = function bar(){}
  5. foo()// 成功
  6. bar() // 不成功,函数名只能在内部调用
  7. // 如果有名称,函数的名称将会成为函数内部的一个局部变量, 可以用来递归
  8. var foo = function bar(n){
  9. if (n === 0) {
  10. return 0
  11. }
  12. bar(--n)
  13. }
  14. foo(3) // 3 2 1 0
  1. // 赋给变量
  2. var s = function () {}
  3. s()
  4. // 赋值给对象的属性
  5. var o = {
  6. prop: function () {}
  7. }
  8. // 函数做为参数
  9. function func(callback) {
  10. callback()
  11. }
  12. // 作为数组元素
  13. var a = [function (x){x*x}]
  14. a[0](2) // 4

箭头函数表达式 (=>)

箭头函数看起来和常规的匿名函数很相似,但它们本质上完全不同
箭头函数不能显式地命名,尽管现代运行环境会将箭头函数所赋予 的变量名作为函数名
箭头函数不能用作构造函数,也没有 prototype 属性,这意味着不能对它们使用 new 关键字;
箭头函数会绑定到 所在词法作用域中(最近的函数),因此它们不会改变 this 的指向。this 、arguments 以及 super 均属于所在的父级作用域。 call() 、apply() 、bind() 等方法调用函数时也无法改变 this 的指向。

当需要定义任何情况下词法作用域都不改变的匿名函数时,箭头函数很 适合,

定义

([param] [, param]) => { statements } param => expression

  • param

参数名称. 零参数需要用()或_表示. 只有一个参数时不需要括号. (例如 foo => 1)

  • statements or expression

多个声明statements需要用大括号括起来,而单个表达式时则不需要。表达式expression也是该函数的隐式返回值。

参数简写

  1. let hello = () => {
  2. console.log('hello')
  3. }
  4. // 或者
  5. _ => {}
  6. // 只有一个参数,可以省略括号
  7. let hello = item => {
  8. console.log(item)
  9. }

函数体简写

  1. // 如果是表达式的话,可以省略括号
  2. // 表达式计算后返回
  3. let hello = (x,y,z) => x + y + z
  4. hello(1,2,3)
  5. // 隐式返回对象字面量
  6. let hello = (x,y,z) => (x+y+z)
  7. let hello = (x,y,z) => {
  8. return x+y+z
  9. }
  10. // 和变量解构结合
  11. let func = ({value, num}) => ({total: value * num})
  12. // 使用
  13. var result = func({
  14. value: 10,
  15. num: 10
  16. })
  17. console.log(result); // {total: 100}

Function构造函数

不推荐使用 Function 构造函数创建函数,因为它需要的函数体作为字符串可能会阻止一些JS引擎优化,也会引起其他问题。

函数的参数

形参和实参

函数的形参: 定义时候的参数 parameter
函数的实参: 调用是的的参数 arguments

实参是形参一个拷贝,作为函数变量
所以修改引用值的参数的属性,会修改形参
如果对这个引用值的参数赋值了,就不会给改变

  1. var value = 1;
  2. function foo(v) {
  3. v = 2;
  4. console.log(v); //2
  5. }
  6. foo(value);
  7. console.log(value) // 1

// 按引用传递

  1. var obj = {
  2. value: 1
  3. };
  4. function foo(o) {
  5. // o 和 obj 都指向{value: 1}这个对象
  6. // 但是 o和 obj 不是同一个变量
  7. // 相当与 var o = obj
  8. o.value = 2;
  9. console.log(o.value); //2
  10. }
  11. foo(obj);
  12. console.log(obj.value) // 2

//其实是共享传递,参数如果是基本类型是按值传递,如果是引用类型按共享传递。

  1. var obj = {
  2. value: 1
  3. };
  4. function foo(o) {
  5. o = 2;
  6. console.log(o); //2
  7. }
  8. foo(obj);
  9. console.log(obj.value) // 1

参数类型

  • 传值 (基本数据类型值 primitive type,比如Undefined,Null,Boolean,Number,String。)
  • 传对象 (引用类型值 也就是对象类型 Object type,比如Object,Array,Function,Date等。)

函数默认参数

es5

  1. function f (x,y) {
  2. y = (typeof y !== 'undefined') ? y : 10
  3. // 或者是y = y || 10; y 传了‘’就不行
  4. return x + y
  5. }
  6. f(1) // 11

es6

  • 如果默认参数在最后面
  1. function foo (a, b, c = 1) {
  2. console.log(a, b, c)
  3. }
  4. foo(1, 2)
  • 如果默认参数在中间 ```javascript function foo1 (a, b = 1, c) { console.log(a, b, c) }

foo1(1, 2) // => 1,2 undefined foo1(1, undefined, 2) // => 1, 1, 2 foo1(1, null, 2) // => 1, null, 2 foo1(1, NaN, 2) // => 1, null, 2 // 上面的结果显示了函数参数的优先级, // 函数参数的优先级:undefined < 默认参 < 除undefined以外的实参 (NaN / null)

  1. 默认参数就是可选的参数,放在最后<br />一般把可选参数做成对象,放在最后
  2. - 默认参数可以是表达式
  3. ```javascript
  4. function foo (x, y = 7, z = x + y) {
  5. return x * 10 + z
  6. }
  7. foo(1, undefined, 2) // 12
  8. foo(1) // 18
  9. foo(1,9) // 20

// 但是参数不定

函数不定参数

arguments

arguments 是 es5的做法,不推荐使用

  • arguments 是 arraylike,object。不是 array
    在js中,允许函数定义参数与传入的参数的数量不一致
    函数运行时执行的时候有一个隐藏参数arguments,它包括了函数所要调用的参数
    它不是数组,typeof arguments 返回的是’object’

虽然可以用length和index,所有的 Array.prototype的方法都不能用,比如但push,pop,arguments.forEach

  1. Array.isArray(arguments) // ==> false
  2. typeof arguments // ==> 'object'
  1. function sum2(x,y) {
  2. arguments[0] = 1;
  3. let sum = x + y
  4. return sum
  5. }
  6. console.log(sum2(2,2));

意外修改了 参数 x,使用前最好拷贝下

  1. function sum3(x,y) {
  2. let arr = [].slice.call(arguments)
  3. console.log(arr)
  4. arr[0] = 1;
  5. let sum = x + y
  6. return sum
  7. }
  • 如何转化成 array
    转换成标准的数组
    var args = Array.prototype.slice.call(arguments);
    手工拷贝
  1. var args = []
  2. var len = arguments.length;
  3. while ( len-- ) {
  4. args[ len ] = arguments[ len ];
  5. }

直接使用
Array.prototype.forEach.call(arguments, function (item) {})
Array.from(arguments).forEach(function (item) {})

rest parameter

es6使用…扩展符替代 arguments
扩展运算符(…):可以理解成一个默认的遍历器
如果函数的最后一个命名参数以…为前缀,则在函数被调用时,该形参会成为一个数组,数组中的元素都是传递给该函数的多出来的实参的值。这代表多出来的实参的值

  1. // 所有的参数都放在这个 nums 中
  2. // ...nums 就是把 nums 的参数数据解构成参数
  3. // ..nums 相当于 num0,num1...
  4. function sum (base,...nums) {
  5. let num = base;
  6. nums.forEach(function (item) {
  7. num += item
  8. })
  9. return num
  10. }
  11. sum(1,2,3) // 9

rest parameter 和 arguments 对象的区别

  • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
  • arguments 对象不是一个真实的数组,而剩余参数是真实的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach,pop。
  • arguments 对象对象还有一些附加的属性 (比如callee属性)。
  • 修改了arguments,可能会意外修改了参数

    函数自定义属性

函数也是一种特殊的对象,也有属性

  1. var func = function () {
  2. return func.count += 1
  3. }
  4. func.count = 0;
  5. func.count // 0
  6. var f1 = new func()
  7. func.count // 1

count是 func 函数的属性,调用依次 func 函数,count 增加1,注意 count 在 func 函数上,不在实例对象上

作为命名空间的函数

因为以前的 js 没有块作用域,所以简单定义一个函数作为临时的命名空间

  1. function mymodule() {
  2. }
  3. mymodule()

这样 mymodule还是全局变量

  1. (function () {
  2. })()

块级函数

非严格模式下的块级函数

不要用

严格模式下

this 指针

  1. let test = {
  2. name: 'test',
  3. say: function () {
  4. console.log(this.name)
  5. }
  6. }
  7. test.say(); // test
  1. let test = {
  2. name: 'test',
  3. say: () => {
  4. console.log(this.name, this)
  5. }
  6. }
  7. // this 是宿主,全局对象
  8. test.say(); // undefined window
  1. let _this = this
  2. let test = {
  3. name: 'test',
  4. say: () => {
  5. console.log(_this.name, _this)
  6. }
  7. }
  8. // this 是宿主,全局对象
  9. test.say(); // undefined window
  1. //匿名函数
  2. var obj = {
  3. birth : 1996,
  4. getAge : function(){
  5. var b = this.birth;
  6. console.log(b) // 1996
  7. var fn = function(){
  8. return (new Date().getFullYear() - this.birth);
  9. }
  10. return fn();
  11. }
  12. }
  13. obj.getAge();//NaN
  14. // return (new Date().getFullYear() - this.birth);
  15. // fn()的上下文环境是全局对象
  16. //箭头函数
  17. var obj = {
  18. birth : 1996,
  19. getAge : function(){
  20. var f = () => new Date().getFullYear() - this.birth;
  21. return f();
  22. }
  23. }
  24. obj.getAge();//24
  25. var obj = {
  26. birth : 1996,
  27. getAge : function(){
  28. var f = function () {
  29. return new Date().getFullYear() - this.birth;
  30. }
  31. return f.call({birth: 2000});
  32. }
  33. }
  34. obj.getAge();//20
  35. var obj = {
  36. birth : 1996,
  37. getAge : function(){
  38. var _this = this
  39. var f = function () {
  40. return new Date().getFullYear() - _this.birth;
  41. }
  42. return f();
  43. }
  44. }
  45. obj.getAge();//24
  46. //由于箭头函数中的this已绑定了词法作用域,
  47. //因此call()或者apply()调用箭头函数时,传入的第一个参数无效。
  48. var obj = {
  49. birth : 1996,
  50. getAge : function(){
  51. var f = () => new Date().getFullYear() - this.birth;//this.birth仍为1996,而非2000
  52. return f.call({birth: 2000});
  53. }
  54. }
  55. obj.getAge();//24

普通函数和构造函数的区别

定义:如果一个函数(使用new运算符)用来初始化一个对象,称为构造函数(constructor)
类比:java中使用与类名同名的函数作为构造函数

箭头函数和普通函数的区别

  1. 箭头函数没有 this
    所以需要通过查找作用域链来确定 this 的值。
    这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。
    因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向,可以看一个例子:
  1. var value = 1;
  2. var result = (() => this.value).bind({value: 2})();
  3. console.log(result); // 1
  1. 没有 arguments,但是可以访问外围函数的 arguments 对象
  1. function constant() {
  2. return () => arguments[0]
  3. }
  4. var result = constant(1);
  5. console.log(result()); // 1

那如果我们就是要访问箭头函数的参数呢?

你可以通过命名参数或者 rest 参数的形式访问参数:

  1. let nums = (...nums) => nums;
  1. 不能通过 new 关键字调用
    JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。

当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。

当直接调用的时候,执行 [[Call]] 方法,直接执行函数体。

箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。

  1. var Foo = () => {};
  2. var foo = new Foo(); // TypeError: Foo is not a constructor
  1. 没有原型,没有 super
    由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。

var Foo = () => {};
console.log(Foo.prototype); // undefined

没有重载

  1. function aa (a) {return a};
  2. function aa (a,b) {return a+b};
  3. aa(1) // NaN
  4. aa(1+2) // 3

相当与

  1. var aa = function (a) {return a};
  2. var aa = function (a,b) {return a+b};
  3. aa(1) // NaN
  4. aa(1+2) // 3

总结

箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数(non-method functions),并且它们不能用作构造函数。
对象属性中的函数就被称之为 method,那么 non-mehtod 就是指不被用作对象属性中的函数了,可是为什么说箭头函数更适合 non-method 呢?

  1. var obj = {
  2. i: 10,
  3. b: () => console.log(this.i, this),
  4. c: function() {
  5. console.log( this.i, this)
  6. }
  7. }
  8. obj.b();
  9. // undefined Window
  10. obj.c();
  11. // 10, Object {...}

自执行函数

  1. (function(){
  2. console.log(1)
  3. })()
  4. (() => {
  5. console.log(1)
  6. })()

函数表达式和函数声明的区别

在预编译阶段,遇到函数声明会被添加到 AO 中

  1. a()
  2. function a() {
  3. console.log(1)
  4. }
  5. function a() {
  6. console.log(2)
  7. }
  8. a();
  9. var a = function () {
  10. console.log(3)
  11. }
  12. a()
  13. // 2 2 3

执行没有报错

在预编辑阶段,遇到 var 定义的变量,指向 undefined 添加到 AO 中

  1. foo() // foo is not a function
  2. var foo = function () {
  3. console.log('foo1');
  4. }
  5. foo(); // foo1
  6. var foo = function () {
  7. console.log('foo2');
  8. }
  9. foo(); // foo2
  10. // 创建全局上下文执行
  11. // 扫描所有的 var
  12. gc = {
  13. vo: {
  14. foo:undefined
  15. }
  16. }
  17. // 所有的声明都没了