第四章 函数

函数

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 记忆

通过缓存变量,从而避免无谓的重复计算;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 继承 一、混入式继承 二、原型继承 利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的...
    magic_pill阅读 1,050评论 0 3
  • 函数的基本知识 函数是对一系列计算或操作的过程的抽象函数可以把大的计算任务分解成若干个较小的任务,程序设计人员可以...
    阳光下的蚂蚁阅读 368评论 0 0
  • 占坑
    米线儿_2017阅读 229评论 2 0
  • 年末岁尾,几个商场大力推销商品,广告铺天盖地,来就送,一百万免费产品送给你。买五百送五百,买一千送一千一,晚上营业...
    买喜堂阅读 77评论 0 4