概述
函数是一个可以执行代码的对象。每个函数都是 Function 类型的实例,并且都与其他引用类型一样具有属性和方法。
每个 Function 的实例的 __proto__
都指向了 Function.prototype(原型),函数原型(Function.prototype)的 __proto__
指向了 Object.prototype(对象的原型)
定义函数
-
函数声明
function name( param1[ , param2[, ...] ] ){ return }
如果
return
省略,那么浏览器将自动添加return undefined
function
是关键字,它只能用来声明函数,而var
则可以用来声明7 种数据类型(number、string、boolean、null、undefined、symbol、 object)中的任意一种。 -
函数表达式
var x = function( param1[ , param2[, ...] ] ){ return }
匿名函数需要赋给变量,之后才能调用。这个匿名函数称为函数表达式
-
混合式
var fn = function name( param1[ , param2[, ...] ] ){ return }
混合式中,函数名 name 只在函数体内部有效,指代函数表达式本身,在函数体外部无效。
-
Function 构造函数
最后一个参数被始终看成函数体,而前面的参数则枚举了新函数的参数。var fn = new Function( 'param1'[ , 'param2'[,' ...'] ] , functionBoby ){ return }
例子:
let n = 1; let fn = new Function( 'x' , 'y' , 'return x + ' + n + ' + y' ); fn( 1,2 ) //4,此时的 n 是1
-
箭头函数(ES6)
(param1[ , param2[, ...] ])=> { return }
形参和实参
- parameters ==> 形参,定义时的占位符
- arguments ==> 实参,调用时传入的参数
说明
- 函数会在执行完 return 语句之后停止并立即退出,因此,位于 return 语句之后的任何代码都永远不会执行。
- 没有传递值的命名参数将自动被赋值 undefined
- 所有参数传递的都是值,不可能通过引用传递参数
- 验证函数,使用
typeof xxx === 'function'
函数声明和函数表达式
- 函数声明
浏览器解析器会率先读取函数声明(声明前置),并使其在执行任何代码之前可用 - 函数表达式
必须要等到浏览器解析器执行到它所在的代码行,才会被解析执行
声明前置
先声明变量,后声明 function,如果 function 名字和 var 声明名字一样
function 优先。如果赋值就按照赋值的类型。
API
自身 API
-
name
==> 返回函数实例的名称
-
length
==> 指明函数的形参个数
Function.prototype API
-
call()
==> 调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)
作用:-
改变 this 值
- 操作参数
语法:
fun.call(this, arg1, arg2, ...)
参数:
-
this :函数运行时的指定的 this 值。
-
非严格模式下,若 this 为 null 或者 undefined ,则 this 自动指向全局对象(global[ window ]),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
-
严格模式下,若 this 为 undefined ,则 this === undefined
此处 call 的时候指明了 this === 1,例子不明显,应该 fn.call(),此时便可以打印出 undefined
-
-
arguments :伪数组,元素为函数的参数
- 函数中的参数在内部是用一个伪数组(
arguments
)来表示,在函数内部通过arguments
对象来访问参数从而获取传递给函数的每一个参数。 -
arguments
可以被用作被调用对象的所有未指定的参数。可以使用arguments来把所有的参数传递给被调用对象。 -
arguments
对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。
- 函数中的参数在内部是用一个伪数组(
-
-
apply()
==> 调用一个函数, 其具有一个指定的this
值,以及作为一个数组(或类似数组的对象)提供的参数。
作用:和 call() 一样- 改变 this 值
- 操作参数
语法:
fun.apply(this, arguments)
使用实例:
-
实例1:不关心 this ,所以将 this 传为 null
-
实例2:关心 this ,this === arr1
说明:
call()
和apply()
的区别
apply()
与call()
作用相同,不同之处在于提供参数的方式。
apply()
使用参数数组,可以将数组里的元素拆分传入参数
call()
使用一组参数列表,一个一个传递参数
apply
可以使用数组字面量,也可以使用arguments
对 象,arguments
是一个函数的局部变量。
-
bind()
==> 创建一个新的函数, 当被调用时,将其this
关键字设置为提供的值,返回新创建的函数。在调用新函数时,在任何提供之前提供一个给定的参数序列。
作用:- 切换上下文(this)
- 科里化
语法:
fun.bind( this [ , arg1 [ , arg2 [ , ... ] ] ] )
使用实例:
- 实例1:基本用法
- 实例2:实际应用
- 实例3:利用 this + apply 实现 bind
new
- 创建一个新对象 obj ,对象
__proto__
指向构造函数的prototype
对象. __proto__ === 构造函数.prototype
- 调用 call()/apply()/bind()
Call Stack(调用栈)
栈是一种数据结构,特点是先进后出,js 代码执行时都遵循 Call Stack 的规则。如果压栈太多会导致错误(Stack Overflow[ 栈溢出 ])
作用域 和 作用域链
scope(范围、视野、眼界)
如果在函数作用域中没有写 var
直接写 a = 3
1. 优先赋值
2. 沿着作用域链寻找 a 的声明
3. 若作用域链中没有 a 的声明,则声明全局变量并赋值
变量提升
将作用域链中首先要做变量提升
例子1:
var a = 1;
function fn(){
console.log( a );
var a = 2;
}
fn.call(); // a === undefined
例子2:
var a = 1;
function fn1(){
console.log( a ); // a === undefined
var a = 2;
fn2.call();
}
function fn2(){
console.log( a ); // a === 1
}
fn1.call();
例子3:
var obj = { name: ' obj ' }
function fn1(){
function fn2(){
console.log( this ); // this === window
}
fn2.call();
}
fn1.call( obj );
例子4:
var liTags = document.querySelectAll( ' li ' );
for( let i = 0, len = liTags.length; i < len; i++ ){
liTags[ i ].onclick = function(){
console.log( i ) // 打印出的 i 都是最后的数字
}
}
this 值
this 的调用主要有两种方式,一种是函数(function),另一种是作为对象的方法(methods)
判断 this 值就看函数怎么被调用,之后转化为 call 形式
例子1:作为对象的方法调用
function fn(){
console.log(this)
}
let obj = {
a: 'a',
fn: fn
}
obj.fn() // this === obj
例子2:作为对象属性的深层次嵌套
function fn(){
console.log(this)
}
let obj = {
a: 'a',
wrapper: {
b: 'b',
fn: fn
}
}
obj.wrapper.fn() // this === { b: 'b', fn: fn } === obj.wrapper
例子3:
let obj = {
a: 'a',
fn () {
console.log(fn)
}
}
function fncb(cb){
cb()
}
fncb(obj.fn) // this === 全局变量
闭包
如果一个函数使用了它范围外的值,那么这个函数 + 这个变量就叫做闭包。
立即调用函数
window下有很多全局属性
当定义变量的时候,有可能不小心覆盖了 window 的全局属性,为了避免覆盖 window 上的属性,可以:
- 不用全局属性
- 用局部变量
- 使用立即调用函数,从而使用局部变量
() => { }.call() () => { }()
- 但是如果像上述写法,浏览器会报错,所以要这么写:
(() => { }.call() ) (() => { } ).call() -() => { }.call() +() => { }.call() !() => { }.call() ~() => { }.call() { let } // let 作用域在块级作用域中
套路
如果同一个函数被多次声明,后面的声明就会覆盖前面的声明
递归:函数调用自身
JavaScript将函数看做一种值,函数只是一个可以执行的值
-
当函数表达式和函数声明同时有的时候
不能在条件语句中声明函数(变量提升,是的 if 无效)
函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
参数传值传递,内存中体现原始值的拷贝。
参数传址传递(复杂类型的值),内存中体现原始值的地址,唯一一种不改变参数的是替换掉整个参数,这时不会影响到原始值(添加属性或方法会影响到原始值)如果有同名的参数,则取最后出现的那个值
eval命令的作用是,将字符串当作语句执行。eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。为了防止这种风险,JavaScript 规定,如果使用严格模式,eval内部声明的变量,不会影响到外部作用域