函数是指将代码分割成可复用的片段
<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]]
var a = 1;function fun(){var b = 1;const p = function p () {console.log(b)console.dir(p)}p();}fun();

https://segmentfault.com/a/1190000012842459
[[FunctionLocation]]
类似于debugger功能,可以很容易的查找到此函数的代码位置,
描述
在JavaScript中,每个普通函数其实都是一个Function对象。
(function(){}).constructor === Function // true(() => {}).constructor === Function // true(class {}).constructor === Function // true(async function (){}).constructor === Function // false(function *(){}).constructor === Function // false(async function *(){}).constructor === Function // false
如果一个函数中没有使用return语句,则它默认返回undefined。
函数定义
函数声明语句
函数名是变量名,指向函数对象
- 一对圆括号里面指定参数
- 一堆花括号里面是函数体,调用函数的时候执行
- this指向函数的调用上下文,
- 函数是动态作用域,在运行时绑定
- 函数声明语句是函数提升,声明和函数体都会提前,重名还会被覆盖
function foo() {console.log('foo1');}foo(); // foo2function foo() {console.log('foo2');}foo(); // foo2// 预编译 定义 foo,重名第二个覆盖第一个gc = {vo: {foo: reference -> function foo() { console.log('foo2');}}}// 执行的时候略过预编译声明的函数
函数表达式
- 函数名可选,指代函数对象本身,没有函数名的叫匿名函数,当函数只使用一次用 IIFE
- 函数定义表达式是变量提升,跟其他用 var 定义的变量一样,只是把变量的声明提前了,变量的初始化代码还在原来的位置
//匿名函数表达式var foo = function (){}//命名函数表达式var foo = function bar(){}foo()// 成功bar() // 不成功,函数名只能在内部调用// 如果有名称,函数的名称将会成为函数内部的一个局部变量, 可以用来递归var foo = function bar(n){if (n === 0) {return 0}bar(--n)}foo(3) // 3 2 1 0
// 赋给变量var s = function () {}s()// 赋值给对象的属性var o = {prop: function () {}}// 函数做为参数function func(callback) {callback()}// 作为数组元素var a = [function (x){x*x}]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也是该函数的隐式返回值。
参数简写
let hello = () => {console.log('hello')}// 或者_ => {}// 只有一个参数,可以省略括号let hello = item => {console.log(item)}
函数体简写
// 如果是表达式的话,可以省略括号// 表达式计算后返回let hello = (x,y,z) => x + y + zhello(1,2,3)// 隐式返回对象字面量let hello = (x,y,z) => (x+y+z)let hello = (x,y,z) => {return x+y+z}// 和变量解构结合let func = ({value, num}) => ({total: value * num})// 使用var result = func({value: 10,num: 10})console.log(result); // {total: 100}
Function构造函数
不推荐使用 Function 构造函数创建函数,因为它需要的函数体作为字符串可能会阻止一些JS引擎优化,也会引起其他问题。
函数的参数
形参和实参
函数的形参: 定义时候的参数 parameter
函数的实参: 调用是的的参数 arguments
实参是形参一个拷贝,作为函数变量
所以修改引用值的参数的属性,会修改形参
如果对这个引用值的参数赋值了,就不会给改变
var value = 1;function foo(v) {v = 2;console.log(v); //2}foo(value);console.log(value) // 1
// 按引用传递
var obj = {value: 1};function foo(o) {// o 和 obj 都指向{value: 1}这个对象// 但是 o和 obj 不是同一个变量// 相当与 var o = objo.value = 2;console.log(o.value); //2}foo(obj);console.log(obj.value) // 2
//其实是共享传递,参数如果是基本类型是按值传递,如果是引用类型按共享传递。
var obj = {value: 1};function foo(o) {o = 2;console.log(o); //2}foo(obj);console.log(obj.value) // 1
参数类型
- 传值 (基本数据类型值 primitive type,比如Undefined,Null,Boolean,Number,String。)
- 传对象 (引用类型值 也就是对象类型 Object type,比如Object,Array,Function,Date等。)
函数默认参数
es5
function f (x,y) {y = (typeof y !== 'undefined') ? y : 10// 或者是y = y || 10; y 传了‘’就不行return x + y}f(1) // 11
es6
- 如果默认参数在最后面
function foo (a, b, c = 1) {console.log(a, b, c)}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)
默认参数就是可选的参数,放在最后<br />一般把可选参数做成对象,放在最后- 默认参数可以是表达式```javascriptfunction foo (x, y = 7, z = x + y) {return x * 10 + z}foo(1, undefined, 2) // 12foo(1) // 18foo(1,9) // 20
// 但是参数不定
函数不定参数
arguments
arguments 是 es5的做法,不推荐使用
- arguments 是 arraylike,object。不是 array
在js中,允许函数定义参数与传入的参数的数量不一致
函数运行时执行的时候有一个隐藏参数arguments,它包括了函数所要调用的参数
它不是数组,typeof arguments 返回的是’object’
虽然可以用length和index,所有的 Array.prototype的方法都不能用,比如但push,pop,arguments.forEach
Array.isArray(arguments) // ==> falsetypeof arguments // ==> 'object'
function sum2(x,y) {arguments[0] = 1;let sum = x + yreturn sum}console.log(sum2(2,2));
意外修改了 参数 x,使用前最好拷贝下
function sum3(x,y) {let arr = [].slice.call(arguments)console.log(arr)arr[0] = 1;let sum = x + yreturn sum}
- 如何转化成 array
转换成标准的数组
var args = Array.prototype.slice.call(arguments);
手工拷贝
var args = []var len = arguments.length;while ( len-- ) {args[ len ] = arguments[ len ];}
直接使用
Array.prototype.forEach.call(arguments, function (item) {})
Array.from(arguments).forEach(function (item) {})
rest parameter
es6使用…扩展符替代 arguments
扩展运算符(…):可以理解成一个默认的遍历器
如果函数的最后一个命名参数以…为前缀,则在函数被调用时,该形参会成为一个数组,数组中的元素都是传递给该函数的多出来的实参的值。这代表多出来的实参的值
// 所有的参数都放在这个 nums 中// ...nums 就是把 nums 的参数数据解构成参数// ..nums 相当于 num0,num1...function sum (base,...nums) {let num = base;nums.forEach(function (item) {num += item})return num}sum(1,2,3) // 9
rest parameter 和 arguments 对象的区别
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
- arguments 对象不是一个真实的数组,而剩余参数是真实的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach,pop。
- arguments 对象对象还有一些附加的属性 (比如callee属性)。
- 修改了arguments,可能会意外修改了参数
函数自定义属性
函数也是一种特殊的对象,也有属性
var func = function () {return func.count += 1}func.count = 0;func.count // 0var f1 = new func()func.count // 1
count是 func 函数的属性,调用依次 func 函数,count 增加1,注意 count 在 func 函数上,不在实例对象上
作为命名空间的函数
因为以前的 js 没有块作用域,所以简单定义一个函数作为临时的命名空间
function mymodule() {}mymodule()
这样 mymodule还是全局变量
(function () {})()
块级函数
非严格模式下的块级函数
严格模式下
this 指针
let test = {name: 'test',say: function () {console.log(this.name)}}test.say(); // test
let test = {name: 'test',say: () => {console.log(this.name, this)}}// this 是宿主,全局对象test.say(); // undefined window
let _this = thislet test = {name: 'test',say: () => {console.log(_this.name, _this)}}// this 是宿主,全局对象test.say(); // undefined window
//匿名函数var obj = {birth : 1996,getAge : function(){var b = this.birth;console.log(b) // 1996var fn = function(){return (new Date().getFullYear() - this.birth);}return fn();}}obj.getAge();//NaN// return (new Date().getFullYear() - this.birth);// fn()的上下文环境是全局对象//箭头函数var obj = {birth : 1996,getAge : function(){var f = () => new Date().getFullYear() - this.birth;return f();}}obj.getAge();//24var obj = {birth : 1996,getAge : function(){var f = function () {return new Date().getFullYear() - this.birth;}return f.call({birth: 2000});}}obj.getAge();//20var obj = {birth : 1996,getAge : function(){var _this = thisvar f = function () {return new Date().getFullYear() - _this.birth;}return f();}}obj.getAge();//24//由于箭头函数中的this已绑定了词法作用域,//因此call()或者apply()调用箭头函数时,传入的第一个参数无效。var obj = {birth : 1996,getAge : function(){var f = () => new Date().getFullYear() - this.birth;//this.birth仍为1996,而非2000return f.call({birth: 2000});}}obj.getAge();//24
普通函数和构造函数的区别
定义:如果一个函数(使用new运算符)用来初始化一个对象,称为构造函数(constructor)
类比:java中使用与类名同名的函数作为构造函数
箭头函数和普通函数的区别
- 箭头函数没有 this
所以需要通过查找作用域链来确定 this 的值。
这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。
因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向,可以看一个例子:
var value = 1;var result = (() => this.value).bind({value: 2})();console.log(result); // 1
- 没有 arguments,但是可以访问外围函数的 arguments 对象
function constant() {return () => arguments[0]}var result = constant(1);console.log(result()); // 1
那如果我们就是要访问箭头函数的参数呢?
你可以通过命名参数或者 rest 参数的形式访问参数:
let nums = (...nums) => nums;
- 不能通过 new 关键字调用
JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。
当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。
当直接调用的时候,执行 [[Call]] 方法,直接执行函数体。
箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错。
var Foo = () => {};var foo = new Foo(); // TypeError: Foo is not a constructor
- 没有原型,没有 super
由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在 prototype 这个属性。
var Foo = () => {};
console.log(Foo.prototype); // undefined
没有重载
function aa (a) {return a};function aa (a,b) {return a+b};aa(1) // NaNaa(1+2) // 3
相当与
var aa = function (a) {return a};var aa = function (a,b) {return a+b};aa(1) // NaNaa(1+2) // 3
总结
箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super或 new.target。这些函数表达式最适合用于非方法函数(non-method functions),并且它们不能用作构造函数。
对象属性中的函数就被称之为 method,那么 non-mehtod 就是指不被用作对象属性中的函数了,可是为什么说箭头函数更适合 non-method 呢?
var obj = {i: 10,b: () => console.log(this.i, this),c: function() {console.log( this.i, this)}}obj.b();// undefined Windowobj.c();// 10, Object {...}
自执行函数
(function(){console.log(1)})()(() => {console.log(1)})()
函数表达式和函数声明的区别
在预编译阶段,遇到函数声明会被添加到 AO 中
a()function a() {console.log(1)}function a() {console.log(2)}a();var a = function () {console.log(3)}a()// 2 2 3
执行没有报错
在预编辑阶段,遇到 var 定义的变量,指向 undefined 添加到 AO 中
foo() // foo is not a functionvar foo = function () {console.log('foo1');}foo(); // foo1var foo = function () {console.log('foo2');}foo(); // foo2// 创建全局上下文执行// 扫描所有的 vargc = {vo: {foo:undefined}}// 所有的声明都没了
