函数是第一类对象
函数也是对象
JavaScript 中函数拥有对象的所有能力,对象能做的任何一件事,函数也都能做
函数的特殊之处在于,它可以被调用,以便执行某项动作
JS 使用函数式编程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function myFunction ( ) {}let myFunction = function ( ) {}; myArray.push (function ( ) {}); zheng.data = function ( ) {}; function call (myFunction ) { myFunction (); } call (function ( ) {}); function myFunction ( ) { return function ( ) {}; } let myFunction = function ( ) {};myFunction.myname = 'Bobo' ;
回调函数
回调函数,指的是,在程序执行过程中,建立的函数会被其他函数在稍后的某个合适时间点“再回来调用”
函数 A 作为参数 (函数引用)传递到另一个函数 B 中,并且这个函数 B 执行函数A。那么函数 A 叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数
1 2 3 4 5 6 7 8 function B (a ) { return a (); } function A ( ) { console .log ('i am A' ); } B (A);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const text = 'Demo callback!' ;console .log ('before defining functions' );function useless (callback ) { console .log ('in useless function' ); return callback (); } function getText ( ) { console .log ('in getText function' ); return text; } console .log ('before making all the calls' );if (useless (getText) === text) { console .log ('the useless function works! ' + text); } console .log ('after the calls have been made' );
1 2 3 4 5 const button = document .getElementById ('button' );button.addEventListener ('click' , function ( ) { alert ('ha!' ); })
1 2 3 4 5 let values = [8 , 5 , 6 , 0 , 3 , 2 , 9 ];values.sort (function (value1, value2 ) { return value2 - value1; })
函数的属性
函数具有属性,而且这些属性可以被存储任何信息
给函数添加一个新属性后,可以使用该属性修改函数本身
使用场景1:在函数属性中存储另一个函数用于之后的引用和调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let store = { nextId : 1 , cache : {}, add : function (fn ) { if (!fn.id ) { fn.id = this .nextId ++; this .cache [fn.id ] = fn; return true ; } } } function chen ( ) {}function zheng ( ) {}store.add (chen); store.add (zheng); store.add (zheng); console .log ('store:' , store);
使用场景2:用函数属性创建一个缓存(记忆),用于减少不必要的计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function isPrime (value ) { if (!isPrime.caches ) { isPrime.caches = {}; } if (isPrime.caches [value] !== undefined ) { console .log (value + '已缓存' ); return isPrime.caches [value]; } let prime = value !== 0 && value !== 1 ; for (let i = 2 ; i < value; i++) { if (value % i === 0 ) { prime = false ; break ; } } return isPrime.caches [value] = prime; } console .log (isPrime (0 ) ? '0是质数' : '0不是质数' ); console .log (isPrime (1 ) ? '1是质数' : '1不是质数' ); console .log (isPrime (2 ) ? '2是质数' : '2不是质数' ); console .log (isPrime (3 ) ? '3是质数' : '3不是质数' ); console .log (isPrime (4 ) ? '4是质数' : '4不是质数' ); console .log (isPrime (9 ) ? '9是质数' : '9不是质数' ); console .log (isPrime (4 ) ? '4是质数' : '4不是质数' );
函数定义 函数声明
函数声明必须独立,但也能够被包含在其他函数或代码块中
必须有函数名,以便被调用
1 2 3 4 5 6 7 8 9 10 11 12 function zheng ( ) { return 'zheng here' ; } zheng (); function bobo () { function hiddenBobo () { return 'bobo here' ; } return hiddenBobo (); } bobo ();
函数表达式
通常作为其他语句中的一部分
可以赋值给变量和属性,可以作为传递给其他函数的参数或函数的返回值
函数名可选,被调用时可以是赋值给的变量,也可以是函数中的参数等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let myFunc = function ( ) { }; myFunc (function ( ) { return function ( ) { }; }); (function namedFunctionExpression ( ) { })(); (function ( ) { })(); +function ( ) { }(); -function ( ) { }(); !function ( ) { }(); ~function ( ) { }();
箭头函数
(param1, param2) => expression
箭头函数是函数表达式的简化版
只有一个参数时,括号不是必须的
函数可以是一个表达式(省略 return 关键字,返回值即表达式的值),也可以是一个代码块(返回值与普通函数一样:如果没有 return 语句,返回值是 undefined;反之,返回值就是 return 表达式的值)
1 2 3 4 const values = [0 , 3 , 4 , 6 , 9 , 1 ];values.sort ((v1, v2 ) => v2 - v1);
函数的实参和形参
形参是定义函数时所列举的变量
实参是调用函数时所传递给函数的值
当函数调用时,实参会按形参在函数中定义的顺序赋值给形参
如果实参的数量大于形参,那么额外的实参不会被赋值给任何形参
如果形参的数量大于实参,那么额外的形参会被设为 undefined
剩余参数
以…作为前缀的命名参数,放在函数参数的最后一个(否则报错),表示一个不定数量的数组
1 2 3 4 5 6 function multiMax (first, ...remainingNumbers ) { let sorted = remainingNumbers.sort ((a, b ) => b - a); return first * sorted[0 ]; } multiMax (3 , 1 , 3 , 5 );
默认参数
ES6 中,创建默认参数的方式是,为函数的形参赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 function performAction (name, action ) { action = typeof action === 'undefined' ? 'walking' : action; return name + ' ' + action; } function performAction (name, action = 'walking' ) { return name + ' ' + action; } performAction ('zheng' ); performAction ('zheng' , 'running' )
可以为默认参数赋任何值,既可以是数字或者字符串这样的基本类型,也可以是对象、数组,甚至函数这样的引用类型
每次函数调用都会从左到右求得参数的值,并且当对后面的默认参数赋值时可以引用前面的默认参数(可读性差)
使用隐式函数参数 arguments 参数
在函数体内可以通过 arguments 对象来访问这个参数数组,从而获取传递给函数的每一个参数
arguments 对象是类数组(并不是Array的实例),使用 length 属性来确定传进来多少个参数(不是定义函数时的命名参数的个数)
arguments 对象可以和命名参数一起使用,arguments 的值永远与对应的命名参数保持同步(只是值同步,内存空间是独立的)
将 arguments 对象作为函数参数的别名使用时会影响代码的可读性,因此在严格模式("use strict")下无法使用它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function whatever (a, b, c ) { console .log (a === 1 ); console .log (b === 2 ); console .log (c === 3 ); console .log (arguments .length ); console .log (arguments [0 ] === a); console .log (arguments [1 ] === b); console .log (arguments [2 ] === c); console .log (arguments [3 ] === 4 ); console .log (arguments [4 ] === 5 ); } whatever (1 , 2 , 3 , 4 , 5 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function sum ( ) { let sum = 0 ; for (let i = 0 ; i < arguments .length ; i++) { sum += arguments [i]; } return sum; } console .log (sum (1 , 2 )); console .log (sum (1 , 2 , 3 )); console .log (sum (3 , 5 , 6 , 7 )); function sum2 (...numbers ) { let sum = 0 ; for (let i = 0 ; i < numbers.length ; i++) { sum += numbers[i]; } return sum; } console .log (sum2 (5 , 6 , 7 , 8 , 9 ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function func (person ) { console .log (person === 'chen' ); console .log (arguments [0 ] === 'chen' ); arguments [0 ] = 'zheng' ; console .log (person === 'zheng' ); console .log (arguments [0 ] === 'zheng' ); person = 'liu' ; console .log (person === 'liu' ); console .log (arguments [0 ] === 'liu' ); } func ('chen' );
this 参数
调用函数时,this 参数也会默认地传递给函数
this 参数代表函数调用相关联的对象,称之为:函数上下文
this 参数的指向不仅由定义函数的方式和位置决定,还受到函数调用方式的影响
函数调用方式
作为一个函数 getUserInfo() 直接被调用
作为一个方法 userInfo.getName() 关联在一个对象上,实现面向对象编程
作为一个构造函数 new UserInfo() 实例化一个新的对象
通过函数的 apply 方法 getUserInfo.apply(userInfo) 或 call 方法 getUserInfo.call(userInfo)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function getUserInfo (name ) {} getUserInfo ('Chen' );(function (who ) { return who; })('Chen' ); let zhao = { getName : function ( ) { } } zhao.getName (); function Zhao (name ) {} zhao = new Zhao (); getUserInfo.call (zhao, 'Wong' ); getUserInfo.apply (zhao, ['Wong' ]);
this 指向 作为函数直接被调用
非严格模式下,this 指向全局 window 对象
1 2 3 4 function getThis ( ) { return this ; } getThis ();
1 2 3 4 5 function getThis ( ) { 'use strict' ; return this ; } getThis ();
作为方法被调用
当一个函数被赋值给一个对象的属性,并且通过对象属性引用的方式调用函数时,函数会作为对象的方法被调用
当函数作为某个对象的方法被调用时,this 指向该对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function whatsMyContext ( ) { return this ; } console .log (whatsMyContext ()); let getMyThis = whatsMyContext;console .log (getMyThis ()); let zhou = { getMyThis : whatsMyContext } console .log (zhou.getMyThis () === zhou); let wu = { getMyThis : whatsMyContext } console .log (wu.getMyThis () === wu);
作为构造函数被调用
1 2 3 4 5 6 7 8 9 10 11 12 13 function Dog ( ) { this .color = function ( ) { return this ; } return 1 ; } console .log (Dog () === 1 ); let dog1 = new Dog (); console .log (typeof dog1 === 'object' ); console .log (typeof dog1.color === 'function' );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let puppet = { rules : false } function Emperor ( ) { this .rules = true ; return puppet; } let emperor = new Emperor ();console .log (emperor === puppet); console .log (emperor.rules ); function Emperor2 ( ) { this .rules = true ; return false ; } let emperor2 = new Emperor2 ();console .log (emperor2 === false ); console .log (emperor2.rules );
使用 apply 和 call 方法调用
每个函数都可以使用 call 或 apply 方法来显式地 指定任何对象作为函数上下文
通过 apply 或 call 方法调用函数,this 指向 apply 或 call 方法的第一个参数
apply 方法参数:作为函数上下文(this)的对象,作为函数实参的一个数组
call 方法参数:作为函数上下文(this)的对象,作为函数实参的一个或多个列表
常用于调用回调函数时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function juggle ( ) { let result = 0 ; for (let i = 0 ; i < arguments .length ; i++) { result += arguments [i]; } this .result = result; } let juggle1 = {};let juggle2 = {};juggle.apply (juggle1, [1 , 2 , 3 , 4 ]); juggle.call (juggle2, 5 , 6 , 7 , 8 ); console .log (juggle1.result ); console .log (juggle2.result );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function forEach (list, callback ) { for (let i = 0 ; i < list.length ; i++) { callback.call (list[i], i); } } let animals = [ {type : 'cats' }, {type : 'dogs' }, {type : 'birds' } ]; forEach (animals, function (index ) { console .log (animals[index].type ); console .log (this === animals[index]); })
使用箭头函数
箭头函数没有单独的 this 值,this 在箭头函数创建时确定
调用箭头函数时,不会隐式传入 this 参数,而是从定义时的所在函数 继承上下文
如果,箭头函数在构造函数内部,this 指向新创建的对象本身
1 2 3 4 5 6 7 8 9 10 11 12 function Button ( ) { this .clicked = false ; this .click = () => { this .clicked = true ; console .log (button.clicked ); } } let button = new Button ();let elem = document .getElementById ('test' );elem.addEventListener ('click' , button.click );
如果,箭头函数是作为对象字面量的属性在全局中定义的,那么箭头函数内部的 this 值等于全局代码的 this 值,即 window 对象(严格模式下是 undefined)
1 2 3 4 5 6 7 8 9 10 11 12 let button2 = { clicked : false , click : () => { this .clicked = true ; console .log (button2.clicked ); console .log (this === window ); console .log (window .clicked ); } } let elem2 = document .getElementById ('test2' );elem2.addEventListener ('click' , button2.click );
使用 bind 方法
所有函数均可访问 bind 方法,可以创建并返回一个新函数,并绑定到传入的对象上
无论如何调用该函数,this 均被设置为对象本身
bind 方法创建的新函数与原始函数的函数体一致,行为一致
调用 bind 方法不会修改原始函数,而是创建了一个全新的函数
1 2 3 4 5 6 7 8 9 10 let button3 = { clicked : false , click : function ( ) { this .clicked = true ; console .log (button3.clicked ); } } let elem3 = document .getElementById ('test3' );elem3.addEventListener ('click' , button3.click .bind (button3));