2

一、JavaScript函数没有函数重载

1.函数参数arguments对象,类数组对象

正是由于函数体内使用arguments对象接收传递的参数,所以即便你定义的函数只接收两个参数,在调用函数时也未必一定要传递两个参数。

2.函数重载

在其他语言中如Java,所谓函数重载就是方法名相同参数不同的所有方法,因为在Java中只要函数签名(接受的参数类型和数量)不同,就认为是不同的函数。但是在JavaScript中函数没有签名,所以做不到真正的函数重载

3.使用arguments对象来弥补没有函数重载的缺憾

function doAdd(){
    if(arguments.length == 1){
        return arguments[0]+10;
    }else if(arguments.length == 2){
        return arguments[0]+arguments[1];
    }else{
        let sum = 0;
        for(let i = 0; i < arguments.length; i++){
            sum += arguments[i];
        }
        return sum;
    }
}

二、函数声明与函数表达式

1.函数声明语法

function 函数名(参数:可选) {
    函数体
}

如果函数在函数体内,或者位于程序的最顶部的话,那它就是一个函数声明

2.函数表达式

function 函数名(可选)(参数:可选) {
    函数体
}

如果函数作为表达式的一部分那么该函数就是函数表达式;
创建一个function关键字后没有标识符的函数,这种情况创建的函数叫匿名函数

3.函数声明提升

解析器会率先读取函数声明,并使其在执行任何代码之前可访问;而函数表达式则必须等到执行器执行到它所在的代码行,才会真正被解释执行

sayHello();  //hello
function sayHello(){
    console.log('hello');
}

上述代码不会报错,因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升的过程读取并将函数声明添加到执行环境中

sayHello();  //报错
var sayHello = function(){
    console.log('hello');
}

上述代码会产生错误,因为函数位于一个赋值语句中,在执行到函数所在语句之前,变量sayHello中不会保存对函数的引用

三、递归

1.什么是递归函数

递归函数是在一个函数中调用自身所构成的

function recursion(num){
    if(num <= 1){
        return 1;
    }
    else{
        return num * recursion(num-1);
    }
}

上述函数表面上看没什么问题,但是如果做如下操作就会出问题

var temp = recursion;
recursion = null;
temp(10); //报错:recursion不是个函数

第一句代码temp指向了原始函数,recursion不再指向原始函数,调用temp(10)的时候用到了recursion(num-1),但是recursion已经不再指向原始函数,所以运行报错

2.arguments.callee

上述情况使用arguments.callee来解决问题,arguments.callee是一个指向正在执行的函数的指针,使用arguments.callee来代替函数名更保险

function recursion(num){
    if(num <= 1){
        return 1;
    }
    else{
        return num * arguments.callee(num-1);
    }
}

3.命名函数表达式

var recursion = (function recur(num){
    if(num <= 1){
        return 1;
    }
    else{
        return num * recur(num-1);
    }
})

上述代码中即便把函数赋值给另一个变量,函数的名字recur仍然有效,所以不会影响递归

四、闭包

1.闭包的作用域链

闭包是指有权访问另一个函数作用域中的变量的函数

function createComparisonFunction(propertyName){
    return function(object1,object2){
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        if(value1 < value2){
            return -1;
        } else if(value1 > value2){
            return 1;
        } else{
            return 0;
        }
    }
}
var compare = createComparisonFunction('name');
var result = compare({name:'Nicholas'},{name:'Greg'});
compare = null;

在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。createComparisonFunction()函数执行完毕后,其执行环境的作用域链会被销毁但是它的活动对象不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁
图片描述

2.闭包与变量

闭包只能取得包含函数中任何变量的最后一个值,闭包所保存的是整个变量对象,而不是某个特殊的变量

function createFunctions(){
    var result = new Array();
    for(var i = 0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var result = createFunctions();
result.forEach(function(func){
    console.log(func()); //10
})

上述代码每个函数都会返回10,因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以每一个函数内部的i的值都是10。如果上述代码中的var i改成let i就会出现预期的效果,因为let声明的变量i属于for循环块级作用域中不属于createFunctions()函数的活动对象

function createFunctions(){
    var result = new Array();
    for(var i = 0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            }
        }(i);
    }
    return result;
}
var result = createFunctions();
result.forEach(function(func){
    console.log(func()); //10
})

把i作为参数传给num,使得result数组中的每个函数都有自己的num变量副本

五、关于this

1.this对象

this对象是在运行时基于函数的执行环境绑定的,在全局函数中this等于window,而当函数作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window

var name = "The Window";
var object = {
    name: "The Object",
    getNameFunc: function(){
        return function(){
            return this.name;
        }
    }
}
console.log(object.getNameFunc()()); //The Window
var name = "The Window";
var object = {
    name: "The Object",
    getNameFunc: function(){
        var that = this;
        return function(){
            return that.name;
        }
    }
}
console.log(object.getNameFunc()()); //The Object

2.this绑定规则

默认绑定

最常用的函数调用类型:独立函数调用

function foo(){
    console.log(this.a);
}
var a = 2;
foo(); //2

上述代码中,this.a被解析成了全局变量a,这是因为函数调用时应用了this的默认绑定,因此this指向全局对象。虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非严格模式下,默认绑定才能绑定到全局对象;严格模式下与foo()调用位置无关,this会绑定到undefined

隐式绑定

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
obj.foo(); //2

当foo函数被调用时,调用位置会使用obj上下文来引用函数,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象,因此this.a和obj.a是一样的

隐式丢失

一个常见的this绑定问题就是隐式绑定的函数会丢失绑定对象,从而应用默认绑定规则

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
    foo:foo
};
var bar = obj.foo; //函数别名
var a = "global";
bar(); //"global"

虽然bar是obj.foo的一个引用,但是实际上它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定

显式绑定

使用call,apply以及bind方法进行显示绑定

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
foo.call(obj); //2

new绑定

使用new来调用函数被称为构造函数调用,这种函数调用会自动执行如下操作:

  • 创建一个全新的对象
  • 这个新对象会被进行原型连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a){
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2

3.判断this

  • 函数是否在new中调用(new绑定)?如果是的话,this绑定的是新创建的对象。var bar = new foo();
  • 函数是否通过call,apply,bind(显示绑定)调用?如果是的话,this绑定的是指定的对象。var bar = foo.call(obj);
  • 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。var bar = obj.foo();
  • 如果都不是的话使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。var bar = foo();

参考文章:
深入理解JavaScript系列
JavaScript高级程序设计
你不知道的JavaScript


SuRuiGit
264 声望23 粉丝