函数
函数实际上是对象
-
在ECMAScript中,函数实际上是对象,每个函数都是Function类型的实例。
-
因为函数是对象,所以函数名就是指向函数对象的指针。(和其他引用类型一样)。
函数的3种定义方式
-
函数声明的方式定义函数
-
函数表达式的方式定义函数
-
使用箭头函数定义函数(ES6新增)
箭头函数(ES6新增)
ES6新增了使用胖箭头(=>)定义函数表达式的语法。
- 任何可以使用函数表达式定义函数的地方都可以使用箭头函数。
如果只有一个参数,那么箭头函数可以不使用括号,只有在没有参数或有多个参数的情况下,才必须使用括号。
箭头函数也可以不使用大括号,但这样会改变函数的行为。
- 使用大括号,就表示包含函数体,和平常的函数没有什么区别。(使用大括号,想要返回值,必须加上return)。
- 不使用大括号,那么箭头之后只能包含一行代码,并且会隐式返回这行代码的值。(不使用大括号不需要为这一行代码加上return,就会隐式返回)。
箭头函数不适用的情况
- 箭头函数不能使用arguments、super、new.target。
- 箭头函数不能作为构造函数。
- 箭头函数没有prototype属性。(箭头函数没有原型对象)。
函数名
因为函数名就是指向函数的指针,所以它们和其他包含对象指针的变量具有相同的行为,这使得一个函数可能具有多个名称。(可以把包含指向函数指针的变量赋给另外的变量,这样另一个变量也会包含指向函数的指针。)
- 原因是不带括号的函数名会访问函数指针,而不会调用函数。
所有ES6的函数对象都会暴露一个只读的name属性。
- 多数情况下,这个name属性中保存的是一个函数标识符(字符串化的函数名)。
- 如果函数没有名称,那么name属性会显示为空字符串。
- 如果函数是使用Function创建的,那么name属性会显示为"anonymous"。
理解参数
ECMAScript函数既不关心传入参数的个数,也不关心这些参数的数据类型。(因此ECMAScript不存在函数签名)。
-
定义函数时要接收两个参数,并不意味着调用时就要传入两个参数,可以传入一个参数、三个参数甚至不传参数,解释器都不会报错。
-
原因是ECMAScirpt函数的参数在函数内部表现为一个数组,函数并不关心这个数组包含什么。
- 在使用function关键字定义函数的时候,可以在函数内部访问arguments对象,从中取得传进来的每个参数值。(箭头函数不行)。
arguments对象
-
arguments对象是一个类数组对象(但不是Array的实例,类数组是可迭代或具有可索引元素的结构),要确定传进来的参数个数可以访问arguments.length。
- arguments对象可以和命名参数一起使用。(命名参数和arguments对象一一对应,第一个命名参数保存着和arguments[0]相同的值)。
-
如果重写arguments[1]的值,那么这个改变也会同步到第二个命名参数。
- 但这并不表示两者指向同一个内存空间,命名参数和arguments指向不同的内存空间,只是改变会同步。
- 严格模式下,修改arguments[1]不会同步到命名参数,且在严格模式下修改arguments对象会报错。
-
如果是箭头函数,那么传给函数的参数不能通过arguments对象访问,只能通过命名参数访问。
没有重载
ECMAScript函数不能像传统编程那样重载。
-
什么是重载
- 重载是多个函数函数名相同而参数列表不同,重载要求函数签名不同,因为ECMAScript传入的参数只表现为一个数组,所以没有函数签名,也就没有重载。
-
如果ECMAScript定义了两个同名函数,那么后定义的会覆盖先定义的。
-
可以通过arguments对象检查传入参数的数量和类型来模拟函数重载。
函数声明与函数表达式
函数声明和函数表达式的区别
-
JavaScript在任何代码执行之前,会先读取函数声明添加到执行上下文。
- 所以函数声明可以在代码执行到它那一行之前调用函数,这个过程叫做函数声明提升。
-
函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。
函数表达式的function关键字后面没有标识符,表明使用函数表达式创建的是匿名函数(匿名函数也叫拉姆达函数),匿名函数的name属性是空字符串。
函数作为值
因为在ECMAScript中函数名就是变量,所以意味着不仅可以把函数作为一个参数传给另一个函数,还可以在一个函数中返回另一个函数。
函数内部
ES5中函数内部存在2个特殊的对象(每个函数在被调用时都会自动创建这2个特殊对象)。
-
arguments
-
arguments对象是一个类数组对象,包含函数调用时传入的所有参数。(箭头函数没有arguments对象)。
- arguments对象还有一个callee属性,是一个指向arguments对象所在的函数的指针。
-
-
this
-
this对象在标准函数和箭头函数中有不同的行为。
-
在标准函数中,this对象引用的是 把函数当成方法来调用的上下文对象。(在网页的全局作用域调用函数,this对象指向windows)。
-
在箭头函数中,this引用的是定义箭头函数的上下文。
- 有时在事件回调或定时回调(setTimeout)中,this指向的并不是我们想要的对象,而这时只要把回调函数改成箭头函数就可以解决问题。
-
-
ES6中新增了一个函数内部的特殊对象
-
new.target
- new.target属性用于检测函数是否是使用new操作符调用的,如果函数是正常调用的,那么new.target值是undefined,如果函数是使用new操作符调用的,那么new.target将引用被调用的构造函数。
函数属性与方法
ECMAScript中函数是对象,因此函数有属性和方法。
-
每个函数都有2个属性
-
length
-
length属性保存的是函数定义的命名参数的个数。
- 注意arguments对象的length属性保存的是函数传入的参数的个数。
-
-
prototype
- prototype属性是实现原型链的基础。
-
-
函数还有2个方法(这2个方法都会设置调用函数时,函数体内this对象的值)。
-
call()
-
call()的第一个参数是this值,而剩下要传给被调用函数的参数是逐个传递的。(使用call()向函数传参时,参数必须一个一个列出来)。
- call()会将被调用函数体内的this值置为传入的this值。
-
-
apply()
-
apply()接受2个参数,第一个参数是函数内的this值,第二个参数是参数数组(参数数组可以是Array的实例,也可以是arguments对象)。
- apply()将会把被调用函数体内的this值置为传入的this值。
-
-
-
ES5定义了一个新方法来设置函数的作用域(函数的执行上下文)。
-
bind()
- bind()方法会创建一个新的函数实例,这个新函数实例的this值会被绑定到传给bind()的对象。
-
闭包
闭包是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。(闭包是函数)。
-
理解闭包首先需要理解作用域链。
-
什么是作用域链
- 在调用一个函数时,会为这个函数创建一个执行上下文,并创建一个作用域链,然后用arguments对象和其他命名参数来初始化这个函数的活动对象,外部函数的活动对象是内部函数作用域链上的第二个对象,作用域链一直向外串起了所有函数的活动对象,直到全局上下文才终止。
-
作用域链的作用
- 函数在执行时,要从作用域链中查找变量,才能进行读写值。
-
-
闭包的副作用
-
闭包的包含函数的活动对象并不能在它执行完毕之后被销毁,因为闭包的作用域链还保留着对包含函数的活动对象(作用域)。(在包含函数执行完毕之后,它的作用域链会被销毁,但它的活动对象依然存在于内存,直到闭包被销毁才会被销毁)。
- 因为闭包会保留它们的包含函数的作用域,所以闭包比一般函数更占内存。
-
在闭包中使用this对象
-
闭包函数的this会指向window对象。(在严格模式下,this值是undefined)。
-
因为闭包函数根本不可能访问到外层函数的this值,所以闭包的this不会被绑定到外层作用域的this。
-
在闭包中this绑定为外层函数this的2个方法
- 把外层函数的this赋值给变量,这样闭包就可以访问了。
- 使用call()、apply()、bind()。
-
-
XMind: ZEN - Trial Version