函数
JS设计得最出色的就是它的函数的实现。它几乎接近完美。但是,想必你也能预料到,JS的函数也存在瑕疵。
所谓编程,就是将一组需求分解成一组函数与数据结构的技能;
1. 函数对象
每个函数在创建时会附加两个隐藏属性:函数的上下文和实现函数行为的代码;
函数的与众不同之处在于它们可以被调用;
2. 函数字面量
3. 调用
调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数;
JS中一共有4种调用模式,方法调用模式,函数调用模式,构造器调用模式,apply调用模式,这些模式在如何初始化关键参数this上存在差异;
4. 方法调用模式
当一个函数被保存为对象的一个属性时,我们称它为一个方法。
当一个方法被调用时,this被绑定到该对象。
var myObject = {
value: 0,
increment: function( inc ){
this.value += typeof inc === 'number' ? inc : 1;
}
}
myObject.increment();
myObject.value ==> 1
myObject.increment(2);
myObject.value ==> 2
5. 函数调用模式
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的;
var add = function(a, b){
return a + b;
}
var sum = add(3, 4);
以此模式调用函数时,this被绑定到全局对象。这是语言设计上的一个错误!!!。
倘若语言设计正确,那么当内部函数被调用时,this应该仍然绑定到外部函数的this变量。
这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权!
var add = function(a,b){
console.log(this) //此处的this指向window对象!!!
}
var obj = {
age: 21,
value: 2,
double: function(){
var helper = function(){
this.value = this.age;
console.log(this)
}
helper()
}
}
console.log(obj.double()) ==> undefined;
//同样
var obj = {
age: 21,
value: 2,
double: function(){
function helper(){
this.value = this.age;
console.log(this)
}
helper()
}
}
console.log(obj.double()) ==> undefined;
得到undefined的原因:以函数调用模式调用函数时,this被绑定到了全局对象,在helper内部的this是window
注意:之前我一直以为helper内部的this是其内部作用域,所以this只能代表其内部,而不能去访问父元素,今天才知道这个this竟然是一个全局变量!!!
解决办法:
var obj = {
age: 21,
value: 2,
double: function(){
var that = this;
var helper = function(){
that.value = that.age;
console.log(that.value)
}
helper()
}
}
console.log(obj.double()) ==> 21;
6. 构造器调用模式
JS是一门基于原型继承的语言。这意味着对象可以直接从其他对象继承属性。该语言是无类型的;
如果在一个函数面前带上new来调用,那么背地里将会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到那个新对象上。
代码举例:
//注意,按照约定,构造函数命名应该以大写字母开头
var Quo = function(string){
this.status = string;
}
//给 Quo 所有实例提供一个名为get_status的公共方法
Quo.prototype.get_status = function() {
return this.status;
}
//构造一个 Quo 实例
var myQuo = new Quo('confused');
myQuo.get_status() ==> confused;
一个函数,如果创建的目的就是希望结合 new 前缀来调用,那它就被称为构造函数
我不推荐使用这种形式的构造函数。在下一章中我们会看到更好的替代方式(《JavaScript语言精粹》这本书一大特点就是,一个东西讲完后,它会告诉你,这个东西我们不建议使用,要想看到更好的替代方式,请看下一章,真是逼着你去学习下一章!!!)
7. Apply 调用模式
apply方法让我们构建一个参数数组传递给调用函数,它也允许我们选择this的值。
apply方法接收两个参数,第1个是要绑定给this的值,第2个就是一个参数数组。
var add = function(a, b) {
return a + b;
}
//构造一个包含两个数字的数组,并将它们相加
var array = [3,4];
var sum = add.apply(null, array) // sum值为7
参数
当函数被调用时,会得到一个“免费”配送的参数,那就是arguments数组。函数可以通过此参数访问所有它被调用时传递给它的参数列表,包括那些没有被分配给函数声明时定义的形式参数的多余参数。
var sum = function(){
var i, sum = 0;
for(i = 0; i < arguments.length; i++){
sum += arguments[i];
}
return sum;
}
sum(4,8,15,16,23,42) ==> 108
因为语言的一个设计错误,arguments并不是一个真正的数组。它只是一个“类似数组”的对象,arguments拥有一个length属性,但它没有任何数组的方法;
1. 返回
return 语句可用来使函数提前返回,当return被执行时,函数立即返回而不再执行余下的语句;
如果一个函数没有指定返回值,则返回 undefined;
var Vue = function(a){
return this
}
Vue.prototype.returnThis = function(){
return this;
}
var vue = new Vue();
vue.returnThis() ==》 Vue{}
2. 异常
(1) throw
var add = function(a, b){
if( typeof a !== 'number' || typeof b !== 'number' ){
throw {
name: 'TypeError',
message: 'add needs numbers'
};
}
return a + b;
}
add(3,'ww') ==> { name: 'TypeError', message: 'add needs numbers' }
(2) try语句的catch从句
var try_it = function(){
try {
add('seven');
} catch(e) {
document.writenIn(e.name + ':' + e.message);
}
}
try_it();
一个try语句只会有一个捕获所有异常的catch代码块
3. 扩充类型的功能
通过给基本类型增加方法。我们可以极大地提高语言的表现力。
给Number原型增加方法来提取数字中的整数部分
Number.method('integer', function(){
return Math[this < 0 ? 'ceil' : 'floor'](this);
})
(-10/3).integer();
给String原型添加一个移除字符串首尾空白的方法
String.method('trim', function(){
return this.replace(/^\s+|\s+$/g, '');
})
通过给基本类型添加方法,我们可以极大地提高语言表现力。
其实,对于这一点,我个人建议尽量使用Jquery,underscore等库来实现各种基本类型和引用类型的处理。因为在原型链上添加过多方法会出现一些问题,比如结构混乱,代码不好维护等。
4. 递归
递归函数就是会直接或间接地调用自身的一种函数。一般来说,一个递归函数调用自身去解决它的子问题;
递归函数可以非常高效的操作树形结构。比如浏览器的文档对象模型(DOM),每次递归调用时处理指定的树的一段。
5. 作用域
在编程语言中,作用域控制着变量与参数的可见性及生命周期。它减少了命名冲突,并且提供了自动内存管理。
var foo = function(){
var a = 3, b = 5;
var bar = function(){
var b = 7, c = 11;
//此时,a = 3, b = 7, c = 11
a += b + c;
//此时,a = 21, b = 7, c = 11
}
//此时,a = 3, b = 5, c未定义
bar();
//此时,a = 21, b = 5
}
任何一对花括号({和})中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域。
函数作用域就好理解了(__) ,定义在函数中的参数和变量在函数外部是不可见的。
JS并不支持块级作用域,它只支持函数作用域,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的
因为JS缺少块级作用域,只支持函数作用域, 所以,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。因为,在一个函数体内任何位置定义的变量,在函数内部任何地方都可见。
functin test(){
for(var i=0;i<3;i++){
}
alert(i);
}
test();
//因为JS没有块级作用域,所以会弹出 i 为 3,可见,在块外,块中定义的变量i仍然是可以访问的
是否还记得,在一个函数中定义的变量,当这个函数调用完后,变量会被销毁,我们是否可以用这个特性来模拟出JS的块级作用域呢?
function test(){
(function(){
for(var i = 0; i < 3; i++ ){
}
})();
alert(i);
}
test();
会报错:i is not defined
这里,我们把for语句块放到了一个闭包之中,然后调用这个函数,当函数调用完毕,变量i自动销毁,因此,我们在块外便无法访问了
闭包
作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments),这太美妙啦!
一个更有趣的情形是:内部函数拥有比它的外部函数更长的生命周期。
var myObject = {
value: 0,
increment: function( inc ){
this.value += typeof inc === 'number' ? inc : 1;
}
}
如果我们要实现保护myObject中的value值不被非法修改,怎么做?
var myObject = (function() {
var value = 0;
return {
increment: function( inc ){
value += typeof inc === 'number' ? inc : 1;
},
getValue: function(){
return value;
}
};
}());
该函数返回一个对象字面量。返回一个包含两个方法的对象,并且这些方法继续享有访问value变量的特权。
myObject中的value对于increment和getValue方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的!!
我们再来看一个例子:
var quo = function(status){
return {
get_status: function(){
return status;
}
};
};
var myQuo = quo('amazed');
myQuo.get_status();
一个有用的例子:把DOM节点设置为黄色,然后渐变为白色;
var fade = function(node){
var level = 1;
var step = function(){
var hex = level.toString(16);
node.style.backgroundColor = '#FFFF' + hex + hex;
if( level < 15 ){
level++;
setTimeout(step,100)
}
}
setTimeout(step, 100)
}
fade(document.body);
7 模块
通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用,从而缓解这个js的最为糟糕的特性之一所带来的影响
模块形式举例:
var obj = function(){
var seq = 0;
var preFix = '';
return {
set_prefix: function(p){
prefix = String(p)
},
set_seq: function(s){
seq = s;
}
}
}
8 级联
其实就是通过在函数内部 return this 来实现函数的链式调用;
9 柯里化
10 记忆
通过缓存变量,从而避免无谓的重复计算;