(JS基础)Function

定义函数

函数本质是对象。每一个函数都是 Function 类型的实例。
javascript

定义函数有两种方式:函数声明、函数表达式。
java

函数声明

使用 "function" 关键字 (会变量提高,但不建议这样作),代码以下:
编程

// 变量提高
console.log(fn);  // [Function: fn]
// "function
function fn() {
  // 函数内容...
}复制代码

函数表达式

使用 "var" 或 "let" 关键字 ("var"会致使声明提高,一样不建议这样作),代码以下:数组

// 声明提高
console.log(fn1);   // undefined
var fn1 = function () {
  // 处理代码...
}
// "let" 声明不会被提高
console.log(fn1);   // 报错!!!
let fn2 = function () {
  // 处理代码...
}复制代码

其实函数表达式是先定义了匿名函数(也叫拉姆达函数),再将匿名函数赋值给变量。
promise

其余

  • 函数没有重载
JavaScript 的函数 没有重载,但能够经过判断参数个数或参数是否为 undefined 来区别处理。看以下例子:
function plus(num1, num2) {
  if (!num1) {
    return 0
  }
  if (num2) {
    return num1 + num2
  }
  return num1
}
console.log(plus());      // 0
console.log(plus(11));    // 11
console.log(plus(2, 1));  // 3复制代码
  • 尽可能不要使用Funtion()new Funtion()建立函数
let fn1 = new Function('return 1');
let fn2 = Function('return 2');
console.log(fn1(), fn2());    // 1 2复制代码

虽然以上代码无语法错误,但和eval()函数同样,可能会被浏览器阻止
浏览器


ES6

函数参数默认值及解构赋值

参数定义时,能够为其提供默认值。这部份内容和解构赋值有关,点击连接看另外一篇文章。下面是简单的例子:闭包

// 参数的默认值
function fn1(x, y = 2) {
  console.log(x, y)
}
// 参数的解构赋值
function fn2({ x, y }) {
  console.log(x, y)
}
// 参数声明时使用解构赋值
function fn3({x, y = 5} = {}) {
  console.log(x, y);
}
// 使用展开运算符和解构赋值
function fn4(...value) {
  console.log(value)
}
fn1(10);                // 10 2
fn2({ y: 22, x: 11 });  // 11 22
fn3();                  // undefined 5
fn4(1, 2, 3);           // [1,2,3]
复制代码

函数对象的 length 属性

length 属性能够获取没有指定默认值的参数个数。看下面例子:app

function fn1(x, y) { }
function fn2(x, y = 1) { }
console.log(fn1.length);    // 2
console.log(fn2.length);    // 1复制代码

箭头函数 =>

箭头函数的最大特色就是"绑定" this 的指向,使其指向定义时所在的对象。基本语法以下:异步

// 箭头后的圆括号表示直接返回的值
let addOne = (x, y) => (value + 1)
// 能够像普通匿名函数同样,处理和返回值 (单个参数能够省略箭头左边的括号)
let addTen = value => {
  value += 10;
  return value
}
console.log(addOne(1, 2));   // 3
console.log(addTen(1));      // 11复制代码

使用注意点async

  • 箭头函数内部不存在"this"对象,因此"this"指向定义时所在的对象,而不是使用时所在的对象。
  • 不能做为构造函数,即不能使用new运算符。
  • 箭头函数内部不存在"arguments"对象,但可使用扩展运算符(...)。
  • 不能用做 Generator 函数,即不能使用 yield 运算符。

函数对象的 name 属性

函数的 name 属性,返回该函数的函数名。

function foo() {}
foo.name              // "foo"
// 函数赋值,但name仍返回原来函数的名字
baz = foo;
baz.name              // "foo"
// 绑定的函数会被加上"bound "
foo.bind({}).name     // "bound foo"
// 匿名函数,返回空字符串
(function(){}).name   // ""
复制代码

Promise 对象

关于Promise的介绍在另外一篇文章

异步函数 (async function)

"async function" 的本质是Promise对象,Promise的语法糖。因此,一样可使用then()catch()。其内部能使用(也只能在其内部使用)的一个关键字await表示等待该行代码执行结束,而后再继续后面的代码。与Promise不一样,"async function" 就是函数,只有被调用时才会执行,而非定义后立刻执行。下面给出简单例子:

// 2s 以后返回双倍的值
function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}
// 定义async函数
async function testResult() {
    console.log('---2s后打印resolve---');
    let result = await doubleAfter2seconds(30);
    setTimeout(() => {
        console.log('再过1.5s后打印"await"后语句')
    }, 1500)
    console.log(`result:${result}`);
}
testResult(30);
/* 打印结果: ---2s后打印resolve--- result:60 再过1.5s后打印"await"后语句 */复制代码

为了加深理解,能够试试下面的一道题:

async function async1() {
  console.log( 'async1 start' );
  await async2();
  console.log( 'async1 end' );
}
async function async2() {
  console.log( 'async2' );
}
 
console.log( 'script start' );
setTimeout( function () {
  console.log( 'setTimeout' );
}, 0);
async1();
new Promise(function (resolve) {
  console.log( 'promise1' );
  resolve();
}).then(function () {
  console.log( 'promise2' );
});
console.log( 'script end' );复制代码

结果以下:

script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout复制代码

迭代器 (Iterator)

迭代器是一种特殊对象,它具备一些专门为迭代过程设计的专有接口,全部的迭代器对象都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;另外一个是done,它是一个布尔类型的值,当没有更多可返回数据时返回true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next()方法,都会返回下一个可用的值。若是在最后一个值返回后再调用next()方法,那么返回的对象中属性done的值为true,属性value若未定义,则为undefined

以为复杂的话,想一想数组对象,它就是内置迭代器,循环遍历时(如for),就是调用迭代器,每一回的循环都.next().value获取值,直到结束。

下面以Array对象的values()方法获取一个迭代器:

let arr = ['a', 4, { a: 1 }];
let myIterator = arr.values();
console.log(myIterator.next().value);   // 'a'
console.log(myIterator.next().value);   // 4
console.log(myIterator.next().value);   // { a: 1 }
console.log(myIterator.next().done);    // true复制代码


生成器 (Generator)

生成器是生成迭代器的函数,经过function *function*定义,函数中的特有关键字yield,表示当前迭代器返回的值,直到迭代器使用next(),则跳到下一个yield停顿,有点相似于return。下面给出简单例子:

const arr = ['blue', 'green', 'red'];
function *creatIterator(arr) {
  for (let i = 0; i < arr.length; i++) {
    // 表示每次调用都返回arr[i]的值,与return相似
    yield arr[i];
  }
}
const item = creatIterator(arr);
console.log('第一次调用:' + item.next().value);
console.log('第二次调用:' + item.next().value);
console.log('第三次调用:' + item.next().value);
console.log('第四次调用:' + item.next().done);
/* 打印结果: 第一次调用:blue 第二次调用:green 第三次调用:red 第四次调用:true */复制代码


函数体内部的属性

函数被定义时,则自动建立两个属性:argumentsthis,且只能在函数内部使用。函数执行时会建立caller属性,指向调用本函数的外部函数。注意,这三个属性都在严格模式下('use strict')没法使用

arguments 对象

arguments 是一个对应于传递给函数的参数的类数组对象。不是数组,但能够转化成数组。函数内部一定存在的一个对象。看下面一个例子:

function fn () {
  console.log(arguments);
}
fn('a', '', 'c');    // { '0': 'a', '1': '', '2': 'c' }复制代码

能够看出 arguments 依次包含 3 个参数。

arguments 对象内部还有 3 个不可遍历的属性

  • argument.length参数的总个数
  • argument.callee:指向函数自己,可用于递归函数,但严格模式下调用会报错
  • "迭代器":表示可使用 "for-of" 遍历 arguments 对象。

this 对象

通常来讲,this 指向执行本函数的上下文,但能够经过 "apply" 、 "call" 和 "bind" 方法改变指向。更多详细描述请查看另外一篇博文

函数执行时的属性(caller)

当本函数执行时,caller属性指向调用本函数的外部函数;当本函数执行结束后,caller会被赋值为null。看以下例子:

function foo() {
  function fn() {
    console.log('fn内部:',fn.caller);
  }
  function baz() {
    let obj = {}
    obj.myFn = fn;
    let globalFn = fn;
    obj.myFn();
    globalFn();
  }
  baz();
  console.dir('fn外部:',fn.caller);
}
foo();
// fn内部: function baz()
// fn内部: function baz()
// fn外部: null复制代码


递归函数

递归的定义是函数内部调用函数自身。上面提到能够用 "arguments.callee" 获取函数自己,但严格模式下是不容许的,看如下例子:

"use strict";
function fn(num) {
  console.log(num--)
  if (num > 0) {
    // 严格模式下会报错
    // arguments.callee(num);
    // 建议使用
    fn(num);
  }
}复制代码


几个关于函数的概念

头等函数 (first-class functions)

当一门编程语言的函数能够被看成变量同样用时,则称这门语言拥有头等函数。例如,在这门语言中,函数能够被看成参数传递给其余函数,能够做为另外一个函数的返回值,还能够被赋值给一个变量。如下示例都证实 JavaScript 是拥有头等函数。

// 函数复制给变量
let foo = function () { }
// 函数返回函数
function baz() {
  return function () { }
}
// 函数做为参数
function foz(foo) { }复制代码

高阶函数 (higher-order functions)

高阶函数是一个接受其余函数做为参数将函数做为返回值返回的函数。如下给出两种高阶函数的例子。

function fn() { }
// 接受函数做为参数的高阶函数
function hof1(fn) { }
// 以函数作为返回值的高阶函数
function hof2() {
  return function () { }
}复制代码

一元函数 (unary functions)

一元函数是一个只接受一个参数的函数。与之对应的是多元函数 (multivariate functions)。

function uf(value) { }复制代码

柯里化 (currying)

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。

// 未柯里化的函数
function sum(a, b, c, d) {
  return a + b * c - d
}
// 柯里化后的函数
function curryingSum1(a) {
  return function (b) {
    return function (c) {
      return function (d) {
        return a + b * c - d
      }
    }
  }
}
// 箭头函数版的柯里化函数
let curryingSum2 = a => b => c => d => a + b * c - d
// 使用柯里化函数
curryingSum2(1)(2)(3)(4); // 3复制代码

上面例子能够看得出,箭头函数比较适合写柯里化的函数。

函数反作用

函数反作用是指函数在正常工做任务以外对外部环境所施加的影响,函数不只仅只是返回了一个值,并且还作了其余的事情。以下例子:

let myObject = { a: 1 }
function fn() {
  myObject.a = 11
  return true
}复制代码

函数反作用的函数也称为非纯函数 (Impure Function)。

纯函数 (pure functions)

输入输出数据流全是显式(Explicit)的,意思是,函数与外界交换数据只有一个惟一渠道 —— 参数和返回值。函数从函数外部接受的全部输入信息都经过参数传递到该函数内部。函数输出到函数外部的全部信息都经过返回值传递到该函数外部。简单说就是没有反作用的函数

function add1(a) {
  return a + 1
}复制代码


闭包函数

闭包函数指的是有权访问另外一个函数做用域中的变量的函数。这部分是比较大的内容,能够点击连接查看详细。


函数高级用法

本篇不做详述,另开篇幅整理此类容,请点击连接

相关文章
相关标签/搜索