Effective JavaScript :第三章

1.函数调用、方法调用以及构造函数调用只是单个构造对象的三种不同的使用模式。

第一种函数调用模式:

function hello(username){
    return ‘hello,’+ username;
}
hello(‘Keyser Soze’);

第二种模式是方法调用:

var obj = {
    hello:function(){
        return ‘hello,’+ this.username;
    },
    username : ‘Hans Gruber’;
};
obj.hello();

通过某个对象调用方法将查找该方法并将该对象作为该方法的接受者。一个非方法的函数调用会将全局对象作为接受者。

第三种模式是构造函数:就像方法和纯函数一样,构造函数也是由function运算符定义的。

function User(name,passwordHash){
    this.name = name ;
    this.passwordHash = passwordHash;
}

使用new操作符来调用User则视其为构造函数:

var u = new User(‘sfalken’,
    ‘0ef33ae791068ec64b502d6cb0191387’
    );
u.name;    //‘sfalken’

构造函数调用将一个全新的对象作为this变量的值,并隐式返回这个新对象作为调用结果。构造函数的主要职责是初始化该对象。

    

2.将函数作为参数或返回值的函数称为高阶函数:

假设有一个简单的转换字符串数组的操作,我们可以使用循环数组实现:

var names = [‘Fred’,‘Wilma’,‘Pebbles’];
var upper = [];
for(var i = 0, n = names.length; i < n ; i++){
    upper[i] = names[i].toUpperCase();
}
upper;

使用数组便利的map方法,我们可以完全消除循环,仅仅使用一个局部函数就可以实现对元素的逐个转换。

var names = [‘Fred’,‘Wilma’,‘Pebbles’];
var upper = names.map(function(name){
    return name.toUpperCase();
})
upper;    //[‘FRED’,‘WILMA’,‘PEBBLES’];

需要引入高阶函数抽象的信号是出现重复或相似的代码。

 

3.使用call方法自定义接受者来调用方法

使用call方法的三种情况:

①通常情况下,函数或方法的接受者是由调用者的语法决定的。有时需要自定义接受者来调用函数,因为该函数可能并不是期望的接受者对象的属性。当然可以将方法作为一个新的属性添加到接受者对象中。但这种方式不仅让人感觉别扭而且相当危险。幸运的是,函数对象具有一个内置的方法call来自定义接收者,可以通过函数对象的call方法来调用其自身:

f.call(obj,arg1,arg2,arg3);

此行为与直接调用函数自身很类似:

f(arg1,arg2,arg3);

②当调用的方法已经被删除、修改或者覆盖时,call方法就派上用场了。使用hasOwnProperty方法的call方法使调用字典对象中的方法成为可能,即使hasOwnProperty方法并没有存储在该对象中。

var hasOwnProperty = { }.hasOwnProperty ;
dict.foo = 1 ;
delete dict.hasOwnProperty ;
hasOwnProperty.call(dict , ‘foo’);
hasOwnProperty.call(dict ,‘hasOwnProperty’);

③定义高阶函数时,call方法也特别实用。高阶函数的一个惯用方法是接收一个可选的参数作为调用该函数的接受者。例如,表示键值对列表的对象可能提供名为forEach的方法。

var table = {
    entries : [];
    addEntry : function(key , value){
    this.entries.push({ key : key , value : value });
    },
    forEach : function(f, thisArg){
    var entries = this.entries;
    for(var i = 0 , n = entries.length ; i < n; i ++){
        var entry = entries[i];
        f.call(thisArg , entry.key , entry.value , i );
        }
    }
} ;

上述例子允许table对象的使用者将一个方法作为table.forEach的回调函数f,并为该方法提供一个合理的接收者。例如,可以方便的将一个table的内容复制到另一个中。

table1.forEach(table2.addEntry , table2) ;

 

4.使用apply方法通过不懂数量的参数调用函数

Apply方法需要一个参数数组,然后将数组的每一个元素作为调用的单独参数调用该函数。除了参数数组,apply方法指定第一个参数绑定到被调用函数的this变量。由于average函数没有引用this变量,因此,我们可以简单地传递null。

var scores = getAllScores();
average.apply(null,scores);

例如,如果scores有三个元素,那么以上代码的行为与average(scores[0],scores[1],scores[2])一致。

apply方法也可用于可变参数方法。例如,buffer对象包含一个可变参数的append方法,该方法添加元素到函数内部的state数组中。

var buffer = {
    state : [ ] ;
    append : function(){
    for(var i = 0 , n = arguments.length ; i < n ; i ++){
        this.state.push(arguments[i]);
        }
    }
};

5.使用argumen创建可变参数的函数

固定元数版本的averageOfArray函数是很容易实现的:

function averageOfArray(a){
    for(var i = 0 , sum = 0, n = a.length ; i < n ; i ++){
        sum += a[i] ;
    }
    return sum / n ;
}
averageOfArray([2,7,1,8,2,8,1,8]);

提供一个可变参数的函数,委托给固定元数版本来实现可变参数的函数:

function average(){
    return averageOfArray(arguments) ;
}

这样一来,函数的使用者就无需借助apply方法,因为apply方法会降低可读性而且经常导致性能损失。

 

6.永远不要修改arguments对象

arguments对象可能看起来像一个数组,但它不总是表现的像数组。使用[].slice.call(arguments)将arguments对象复制到一个真正的数组中再进行修改。

 

7.使用变量保存arguments的引用

引用arguments时当心函数嵌套层级。绑定一个明确作用域的引用到arguments变量,从而可以在嵌套的函数中引用它。

function values(){
    var i = 0 , n = arguments.length , a = arguments ;
    return {
        hasNext : function(){
            return i < n ;
        },
    next : function(){
        if(i >=n){
            throw new Error(“end of iteration”) ;
        }
        return a[i++] ;
        }
    };
}
var it = values(1,4,1,4,2,1,3,5,6);
it.next();
it.next();
it.next();    

8.使用闭包而不是字符串来封装代码

函数是一种将代码作为数据结构存储的便利方式,这些代码可以随后被执行,可是应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数,字符串表示代码不够灵活的一个重要原因是:它们不是闭包。

假设有一个简单的多次重复用户提供的动作的函数。

function repeat(n , action){
    for(var i = 0 ; i < n ; i ++){
        eval(action) ;
    }
}

该函数在全局作用域会工作的很好,因为eval函数会将出现在字符串中的所有变量引用作为全局变量来解释。

 

9.避免使用非标准的栈检查属性

调用栈是指当前正在执行的活动函数链。在某些旧的宿主环境中,每个arguments对象都含有两个额外的属性:arguments.callee和arguments.caller。前者指向使用该arguments对象被调用的函数;后者指向调用该函数arguments对象的函数。许多环境仍然支持arguments.callee,但它除了允许匿名函数递归地引用其自身之外,就没有更多的用途了。

避免使用非标准的arguments.callee和arguments.caller属性,因为它们不具备良好的移植性。避免使用非标准的函数对象caller属性,因为在包含全栈信息方面,它是不可靠的。

时间: 2024-09-30 18:24:17

Effective JavaScript :第三章的相关文章

Effective JavaScript :第二章

1.熟练掌握闭包 理解闭包要学会三个基本的事实: ①JavaScript允许你引用在当前函数以外定义的变量: 例如: function makeSandwich(){ var magicIngredient = ‘peanut butter’; function make(filling){ return magicIngredient + ‘and’ + filling; } return make(‘jelly’); } makeSandwich(); //‘peanut butter an

JavaScript第三章

JavaScript的核心语言特性是以名为ECMAScript的伪语言的形式来定义的.ECMAScript中包含了所有的基本语法.操作符.数据类型以及完成基本的计算任务所必需的对象. ECMAScript中基本的要素有以下几点: ECMAScript中的基本数据类型包括Undefined.Null.Boolean.Number和String. 与其他语言不同,ECMAScript没有为整数和浮点数值分别定义不同的数据类型,Number类型可用于表示所有数值. ECMAScript中也有一种复杂的

[读书笔记]Effective Java 第三章

覆盖equals方法时请遵守通用约定 这种说法的大意是要说明,Object类中定义的很多默认准则是被许多工具类或是第三方框架共同遵守的标准,一旦改动这样的标准机制,会在后续的使用中产生不稳定因素.equals方法常见用来做以下用途时,不建议对equals方法进行覆盖: 1.判断实例的唯一性 2.提供某种程度的逻辑相等 equals方法满足自反性,对称性,传递性,一致性,非空性.当需要覆盖equals方法时,需要注意以下三点: 1.覆盖equals时总要覆盖hashCode 2.不要企图让equa

[Effective JavaScript 笔记] 第一章:让自己习惯javascript小结

在这里整理一下,每条对应的提示 第1条:了解使用的js版本 确定应用程序支持的js的版本(浏览器也是应用程序噢) 确保使用的js特性是应用程序支持的(要不写了也运行不了) 总是在严格模式下编写和测试代码(面向未来编程) 合并代码时注意不同声明的严格模式(还是用第二种解决方案吧,省心) 第2条:理解JavaScript的浮点数 js的数字都是双精度的浮点数 js的整数仅仅是双精度浮点数的一个子集,不是单独的一个类型 位运算将数字视为32位的有符号整数 当心浮点运算的精度问题 第3条:当心隐式的强制

JavaScript 第三章总结

Getting functional function的特点 function can be reused over and over much more readable function is parameterized 如何定义一个function function addscore(score,score){body;} 首先是关键字:function 然后是函数名:addscore 之后在括号中为两个 parameter ,并且它们不需要在前面加 var, 因为 JavaScript

Effective java 第三章对于所有对象都通用的方法(一) 读书笔记

对于所有对象都通用的方法 覆盖equals时请遵守通用约定 类的每个实例本质上都是唯一的. 不关心类是否提供了逻辑相等的测试功能 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的. 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用. throw new AssertionError() 一般覆盖Object.equals都是值类 但有一种值类不需要覆盖equals方法,即实例受控,确保每个值至多只存在一个对象的类.如枚举 覆盖equals方法,通用约定. 自

从思维导图中学习javascript第三章数组

1对数组的操作方法: 1. push():在数组末尾添加数组 2.unshift():在数组头部添加元素 3.concat()合并两个数组 4pop()删除返回数组的最后一个元素 5shift()删除并返回数组的第一个元素 splice(参数1,参数2,参数3): 删除任意数量的项:1要删除的起始下标2要删除的项数 在制定位置插入指定的项:1.起始下标2.0(不删除任何项)3.要插入的项 替换任意数量的项:1.起始下标2.要删除的项数3.要插入的项 slice(参数1,参数2):从已有数组中选取

JavaScript高级程序设计(第3版)第三章读书笔记

第三章  基本概念 ECMAScript中的一切(变量.函数名和操作符)都区分大小写. 标识符是指变量.函数.属性的名字,或者函数的参数. 标识符的组成规则是:第一个字符必须是一个字母.下划线(_)或一个美元符号($):其他字符可以是字母.下划线.美元符号或数字. ECMAScript标识符采用驼峰大小写格式. ECMAScript注释包括单行注释(//)和块级注释(/*  *  */). ECMAScript 5引入了严格模式的概念,严格模式是为JavaScript定义了一种不同的解析与执行模

Javascript高级程序设计——第三章:基本概念

javascript高级程序设计——第三章:基本概念 一.语法 EMCA-262通过叫做ECMAScript的“伪语言”为我们描述了javascript实现的基本概念 javascript借鉴了C的语法,区分大小写,标示符以字母.下划线.或美元符号($)开头,注释可以用 // 或者/* */ 严格模式: ECMAScript 5引入了严格模式,在严格模式下不确定的行为将得到处理,通过在顶部添加 “use strict”来启用严格模式: function fuc(){ "use strict&qu

《JAVASCRIPT高级程序设计》第三章

<JAVASCRIPT高级程序设计>第三章主要讲述了这门语言的基础概念,内容多而浅,通过思维导图可以帮助我们很好的理清脉络. js函数使用function关键字来声明,以下是一个简单的例子: 1 function sayHi(name, message){ 2 alert("hello," +name + message); 3 } 函数中可以使用return语句指定返回值,如果只有“return;”,则函数在停止执行后,将返回undefined; 定义的函数有2个参数,那