JavaScript语言精粹学习笔记之函数

函数对象

在javascript中函数就是对象,对象字面量产生的对象连接到object.prototype,函数对象连接到function.prototype(该原型对象本身连接到object.prototype),每个函数在创建时附有两个附加的隐藏属性;函数的上下文和实现函数行为的代码

每个函数对象在创建时也随带有一个prototype属性,它的值是一个拥有constructor属性且值即为该函数的对象

因为函数是对象,所以它们可以像任何其他的值一样被使用,函数可以存放在变量、对象和数组中,函数可以被当作参数传递给其他函数,函数也可以再返回函数,而且,因为函数是对象,所以函数可以拥有方法

函数的与众不同之处在于它们可以被调用

函数字面量

通过函数字面量在创建

var add = Function(a,b) {
    return a+b ;
};

函数字面量包括四个部分,第一个是保留字function

第二个部分是函数名,它可以被省略,函数可以用来它的名字来递归地调用自己,此名字也能被调试器和开发工具用来识别函数,如果没有给函数命名,它会被认为是匿名函数。

函数第三部分是包围在圆括号地一组参数,其中每个参数用逗号分隔,这些名称将被定义为函数中地变量,他们不像普通地变量那样被初始化为undifined,而是在该函数被调用时初始化为实际提供的值

第四部分是包围在花括号中地一组语句,这些语句是函数的主体,他们在函数被调用时执行

函数字面量可以出现在任何允许表达式出现的地方,函数也可以被定义在其他函数中,一个内部函数自然可以访问自己的参数和变量,同时它也能方便地访问它被嵌套在其中地那个函数的参数与变量,通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这被称为闭包,它是javascript强大表现力的根基

调用

调用一个函数将暂停当前函数的执行,传递控制权和参数给新函数,除了声明时定义的形式参数,每个函数接受两个附加的参数: this和arguments,参数this在面向对象编程中非常重要,它的值取决于调用的模式,在JavaScript中一共有四种调用模式

方法调用模式

当一个函数被保存为对象的一个属性时,我们称它为一个方法,当一个方法被调用时,this被绑定到该对象,如果一个调用表达式包含一个属性存取表达式(即一个 . 点表达式或 [subscript] 下标表达式),那么它被当作一个方法来调用

var myObject = {
    value: 0,
    increment: function(inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
};
myObject.increment();
console.log(myObject.value);

myObject.increment(2);
console.log(myObject.value);

控制台输入结果如下:

方法可以使用this去访问对象,所以它能从对象中取值或修改该对象,this到对象的绑定发生在调用的时候,通过this 可取得它们所属对象的上下文的方法称为公共方法

函数调用模式

当一个函数并非一个对象的属性时,那么它被当作一个函数来调用:

var add = function(a, b) {
    return a + b;
};
var sum = add(3, 4);
console.log(sum);

控制台输入结果如下:

构造器调用模式

javasc是一门基于原型继承的语言,这意味着对象可以直接从其他对象继承属性,如果在一个函数前带上new调用,那么将创建一个隐藏连接到该函数的prototype成员的新对象,同时this将会绑定到那个新对象上

var Quo = function(string) {
    this.status = string;
};
Quo.prototype.get_status = function() {
    return this.status;
};
var myQuo = new Quo('confused');
console.log(myQuo.get_status());

控制台输入结果如下:

Apply调用模式

函数以可拥有方法

apply方法让我们构建一个参数数组并用其去调用函数,它也允许我们选择this的值,apply方法接受两个参数,第一个是将被绑定给this的值,第二个就是参数数组

var statusObject = {
    status: 'A-OK'
};
var status = Quo.prototype.get_status.apply(statusObject);
console.log(status);

控制台输入结果如下:

参数

当函数被调用时,会得到一个"免费"奉送的函数,那就是arguments数组,通过它函数可以访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数

var sum = function() {
    var i, sum = 0;
    for (i = 0; i < arguments.length; i++) {
        sum += arguments[i];
    }
    return sum;
};
console.log(sum(2, 3, 4, 5));

控制台输入结果如下:

返回

当一个函数被调用时,它从第一个语句开始执行,并在遇到关闭函数体的 } 时结束,那使得函数把控制权交还给该函数的程序部分

return 语句可用来使函数提前返回,当return被执行时,函数立即返回而不再执行余下的语句

一个函数总是会返回一个值,如果没有指定返回值,则返回undefined

如果函数以在前面加上new前缀的方式来调用,且返回值不是一个对象,则返回this(该新对象)

异常

JS提供了一套异常处理机制,异常市干扰程序的正常流程的非正常的事故,当查出这样的事故时,你的程序应该抛出一个异常

var add = function(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw {
            name: 'TypeError',
            message: 'add needs numbers'
        };       
    }
    return a + b;
}

throw语句中断函数的执行,它应该抛出一个exception对象,该对象包含可识别异常类型的name的属性和一个描述message属性,也可以添加其他属性

该exception对象将被传递到一个try语句的catch从句

var try_it = function() {
    try {
        result = add('seven');
        console.log(result);
    } catch {
        console.log(e.name + ':' + e.message);
    }
}
try_it();

 控制台输入结果如下:

 如果在try代码内抛出一个异常,控制权就会跳转到它的catch从句

给类型增加方法

JS允许给语言的基本类型增加方法

通过给基本类型增加方法,我们可以大大提高语言的表现力,因为JavaScript原型继承的动态本质,新的方法立刻被赋予到所有的值(对象实例)上,哪怕值(对象实例)是在方法被创建之前就创建好了。

递归

递归函数会直接或间接地调用自身的一种函数,它将一个问题分解为一组相似的子问题,每一个问题都用一个寻常解去解决,一般来说,一个递归函数调用自身去解决它的子问题

作用域

在编程语言中,作用域控制着变量与参数的可见性及生命周期

大多数使用c语言语法的语言都拥有块级作用域,在一个代码块中定义的所有变量在代码块的外部是不可见的,定义在代码块中的变量在代码执行结束后会被释放掉

但是JS缺少块级作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量

var foo = function() {
    var a = 3,
        b = 5;
    var bar = function() {
        var b = 7,
            c = 11;
        a += b + c;
        return a;
    };
    bar();
    console.log(a);
};
foo();

控制台输入结果如下:

闭包

作用域的好出是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)

var myObject = function() {
    var value = 0;
    return {
        increment: function(inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function() {
            return value;
        }
    }
}();

 我们并没有把一个函数赋值给myObject,而是把调用该函数后返回的结果赋值给它,该函数返回一个包含两个方法的对象,并且这些方法继续享有访问value变量的特权

function quo(status) {
    return {
        get_status: function() {
            return status;
        }
    };
};

var myQuo = quo('amazed');
console.log(myQuo.get_status());

这个quo函数被设计成无须在前面加上new来使用,所以名字也没有首字母大小写,当我们调用quo时,它返回包含get_status方法一个新对象,该对象的一个引用保存在myQuo中,即使quo已经返回了,但get_status方法仍然享有quo对象的status属性的特权,get_status方法并不是访问该参数的一个拷贝,它访问的就是参数本身,这是可能的,也因为该函数可以访问它被创建时所处的上下文环境,这被称为闭包,闭包的本质就是在一个函数内部创建另一个函数。

回调

函数可以让不连续时间的处理变得更容易,假如有这么一个例子,由用户交互开始,向服务器发送请求,最终显示服务端的相应,最淳朴的写法可能会是这样的

request = prepare_the_request();
response = send_request_synchronously(request);
display(response)

这种方式的问题在于网络上的同步请求将会导致客户端进入假死状态,如果网络传输或服务器很慢,响应性的降低将是不可接受的

更好的方式是发起异步的请求,提供一个当服务器的响应达到时将被调用的回调函数,异步的函数立即返回,这样客户端不会被阻塞

request = prepare_the_request();
send_request_synchronously(request, function response() {
    display(response);
});

模块

我们可以使用函数和闭包来构造模块,模块是一个提供接口却隐藏状态与实现的函数或对象,通过使用函数去产生模块,我们几乎可以完全摒弃全局变量的使用,从而缓解这个JavaScript的最为糟糕的特性之一所带来的影响

String.method('deentityify', function() {
    //字符实体表,它映射字符实体的名字到对应的字符
    var entity = {
        quot: '"',
        It: '<',
        gt: '>'
    };
    return function() {
        return this.replace(/&([^&;]+);/g,
            function(a, b) {
                var r = entity[b];
                return typeof r === 'string' ? r : a;
            }
        );
    };
}());

请注意最后一行,我们用()运算法立刻调用我们刚刚构造出来的函数,这个调用所创建并返回的函数才是deentityify方法

模块模式利用了函数作用域和闭包来创建绑定对象与私有成员的关联,在这个例子中,只有deentityify方法才有权访问字符实体表这个数据对象

模块模式的一般形式是:一个定义了私有变量和函数的函数;利用闭包创建可以访问私有变量和函数的特权函数;最后返回这个特权函数,或者把它们保存到一个可以访问的地方

使用模块模式就可以摒弃全局变量得到使用,它促进了信息隐藏和其他优秀的设计实践,对于应用程序的封装,或者构造其它单例对象,模块模式非常有效

function serial_maker() {
    let prefix = '';
    let seq = 0;
    return {
        set_prefix: function(p) {
            prefix = String(p);
        },
        set_seq: function(s) {
            seq = s;
        },
        gensym: function() {
            let result = prefix + seq;
            seq += 1;
            return result;
        }
    };
};
var seqer = new serial_maker();
seqer.set_prefix('Q');
seqer.set_seq(1000);
var unique = seqer.gensym();
console.log(unique);

seqer包含的方法都没有用到this或that,因此没有办法损害seqer,除非调用对应的方法,否则没法改变prefix或seq的值,seqer对象是可变的,所以它的方法可能会被替换掉,但替换后的方法依然不能访问私有成员,seqer就是一组函数的集合,而且那些函数被赋予特权,拥有使用或修改私有状态的能力

级联

有一些方法没有返回值,例如,一些设置或修改某个对象的某个状态却不返回任何值的方法就是典型的例子,如果我们让这些方法返回this而不是undefined,就可以启用级联,在一个级联中,我们可以在单独一条语句中依次调用同一个对象的很多方法

级联可以产生出具备很强表现力的接口,他也能帮助控制那种构造试图一次做太多事情的接口的趋势

套用

函数也是值,套用允许我们将函数与传递给它的参数相结合去产生一个新的函数

记忆

函数可以用对象去记住先前操作的结果,从而避免无谓的运算,这种优化被称为记忆,JavaScript的对象和数组要实现这种优化是非常方便的

对于一种纯函数(即只要是同一种输入就能得到唯一一个相同的结果的函数),我们可以设置记忆函数来节约资源,当输入的参数不变时,我们就可以采用缓存的结果,当输入参数有所变化时才重新计算。一种记忆函数如下所示:

function memorize(fn) {
  var cachedArg;
  var cachedResult;
  return function(arg) {
    if (cachedArg === arg) {
      return cachedResult;
    }
    cachedArg = arg;
    cachedResult = fn(arg);
    return cachedResult;
  };
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值