什么是函数?
函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。
定义函数
函数声明
function 命令声明的代码区块,就是一个函数。(function关键字 + 函数名 )函数名后面圆括号内是传入函数的参数
// 函数声明提升(在执行代码之前会先读取函数声明,可以把函数声明放在调用之后)
sayName(); // 使用 sayName()这种形式,就可以调用相应的代码
function sayName(value){
console.log("fanison");
}
函数表达式
创建一个函数并将它赋值给变量;
因为赋值语句的等号右侧只能放表达式,所以又称函数表达式
var x = function(y) {
return y * y;
}; // 函数表达式在语句的结尾加上分号,表示语句结束
// 没有函数声明提升
sayName(); // 错误:函数还不存在 sayName is not a function
var sayName = function(){
console.log("fanison")
}
Function构造函数
var add = new Function('x','y','return x + y')
// 相当于
function add(x,y){
return x + y;
}
递归
函数可以调用自身,这就是递归。
递归函数是在一个函数通过名字调用自身的情况下构成的。
function recursion(num){
if(num <= 1){
return 1;
}else{
return num * recursion( num - 1 ); // 调用自身
}
}
上面代码中,recursion函数内部又调用了 recurison。
arguments.callee是一个指向正在执行的函数的指针,可以实现对函数的递归调用
function recursion(num){
if(num <= 1){
return 1;
}else{
return num * arguments.callee(num-1); // 使用arguments.callee代替函数名
}
}
使用函数表达式
var recursion = (function f(num){ // 创建名为 f()的命名函数表达式,赋值给变量 recursion
if(num <= 1 ){
return 1;
}else{
return num * f(num -1);
}
});
闭包
闭包与对象
JavaScript
有两种作用域:全局作用域和函数作用域。
函数内部可以直接读取全局变量。
var speed = 0;
function car(){
console.log(speed);
}
car() // 0
上面代码中,函数
car
可以读取全局变量speed
。函数外部无法读取函数内部声明的变量
function car(){
var speed = 0;
}
console.log(speed) // Uncaught ReferenceError: speed is not defined
上面代码中,函数
car
内部声明的变量speed
,函数外是无法读取的。在函数内部再定义一个函数,可以得到函数内的局部变量
function car(){
var speed = 0;
function fn(){
speed++
console.log(speed);
}
}
在函数
car
内部再定义 函数fn
, 此时fn
可以读取car
的局部变量function car(){
var speed = 0
function fn(){
speed++
console.log(speed)
}
return fn // 将fn作为返回值,就可以在函数外部读取它的内部变量
}
var speedUp = car()
speedUp() // 1
speedUp() // 2
上面代码中,函数
car
的返回值就是函数fn
,由于fn
可以读取car
的内部变量,所以就可以在外部获得car
的内部变量了。
闭包就是能够读取其他函数内部变量的函数。可以把闭包简单理解成“定义在一个函数内部的函数”。
典型的闭包是一个函数A内声明并返回一个函数B供外部使用,函数B内用到了A内部的局部变量或者形参。相互引用导致局部作用域不被释放一起构成闭包。
闭包的作用:
- 用来暂存“当时”的状态;
- 用来“封装”一些数据。
this对象
含义
this就是属性或方法“当前”所在的对象
this对象是在运行时基于函数的执行环境绑定的: 全局函数中,this等于window;函数被作为某个对象的方法调用时,this等于该对象;匿名函数的this指向window
var name = "Window name"
var object = {
name: "Object name",
getNameFunc: function(){
return function(){ // 返回匿名函数
return this.name;
}
}
}
console.log(object.getNameFunc()())
创建全局变量 name,创建包含name属性的object对象。object对象的 getNameFunc()方法返回匿名函数,匿名函数返回 this.name。由于this指向window,所以输出“Window name”
var name2 = "Window name2"
var object2 = {
name: "Object name2",
getNameFunc: function(){
var that = this // 把 this 对象赋值给 that 变量,this指向为object2
return function(){
return that.name
}
}
}
console.log(object2.getNameFunc()())
在定义匿名函数前,把this对象赋值给that变量,使用闭包访问that变量,that变量引用object2;所以输出“Object name2”
使用场景
- 全局环境
全局环境使用 this ,它指的就是顶层对象 window
this = window // true
function f(){
console.log(this === window)
}
f() // true
只要是在全局环境下运行, this 就是指顶层对象 window
- 构造函数
构造函数中的 this 指的是实例对象
var Obj = function(p){
this.p = p;
}
var o = new Obj('Hello World');
o.p // "Hello World"
定义构造函数 Obj, this 指向实例对象,所以在构造函数内部定义 this.p,就相当于定义实例对象有一个p属性。
- 对象的方法
对象的方法里面包含 this ,this的指向就是方法运行时所在的对象。
var obj = {
foo: function(){
console.log(this);
}
};
obj.foo() // obj
绑定this
- Function.prototype.call()
函数实例的 call 方法,可以指定函数内部 this 的指向,然后在所指定的作用域中,调用函数
call方法的参数,应该是一个对象。如果参数为空、null和undefined,默认传入全局对象
var n = 123;
var obj = { n: 456};
function a(){
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456
a函数中的this关键字,如果指向全局对象,返回结果为123。使用cakk方法将this关键字指向obj对象,返回结果为456.
call方法可以接受多个参数,第一个参数就是this指向的对象,后面的参数是函数调用时所需的参数
function add(a,b){
return a + b;
}
add.call(this,1,2) // 3
call方法指定函数add内部的this绑定当前环境,函数参数为1和2,函数add运行后得到3。
- Function.prototype.apply()
apply方法也是改变this指向,然后再调用该函数,它接受一个数组作为函数执行时的参数
第一个参数是this所要指向的对象(若为null或undefined,等于全局对象),第二个参数是一个数组
func.apply(thisValue, [arg1, arg2, ...])
function add(a,b){
return a + b;
}
add.apply(null,[1,2]) // 3
- Function.prototype.bind()
bind()方法用于将函数体内的 this 绑定到某个对象,然后返回一个新函数
如果第一个参数是null
或undefined
,等于将this
绑定到全局对象
var counter = {
count: 0,
inc: function(){
this.count++;
}
};
var func = counter.inc.bind(counter)
func();
counter.count // 1
counter.inc() 方法被赋值给变量 func,使用bind()方法将inc()内部的this绑定到counter
内存泄漏
function assignHandler(){
var element = document.getElementById('idname')
element.onclick = function(){
console.log(element.id)
}
}
创建闭包,闭包会引用包含函数的整个活动对象,匿名函数保存对 assignHandler()活动对象的引用,只要匿名函数存在,element的引用至少是1,所以element占用的内存就永远不会回收。
模仿块级作用域
JS中没有块级作用域(在块语句中定义的变量,实际是在包含函数中创建的而不是语句)
function outputNumbers(count){
for(var i=0;i<count;i++){
console.log(i);
}
var i // 重新声明
console.log('i:'+i)
}
在函数中定义 for 循环,变量i初始值设置为0,循环结束后,i仍存在于 outputNumbers。
即使重新声明变量i,JS也不会报错
可以使用匿名函数来模仿块级作用域,避免变量多次声明
var someFunction = function(){
// 块级作用域
}
someFunction();
先定义一个函数(创建一个匿名函数,把匿名函数赋值给变量 someFunction),然后立即调用它(在函数名称后面添加一对圆括号)
将函数声明转换成函数表达式
(function(){
// 块级作用域
})()
定义一个匿名函数,并立即调用。 将函数声明包含在圆括号中,另一对圆括号会立即调用该函数。
使用场景:
需要临时变量,可以使用私有作用域
function outputNumber(count){
(function(){
for(var i=0;i<count;i++){
console.log(i)
}
})();
console.log(i) // 报错
}
在for循环外部插入一个私有作用域,在匿名函数中定义的变量会在执行结束时被销毁,所以变量i只能在循环中使用。