前段时间看了几道关于前端javascript的面试题目,方觉函数调用模式等基础的重要性。于是,下定决心,好好补补基础,即便不能深入语言的内部设计模式,也要对基本面向对象概念有比较深入的理解。

继续上一篇博文《编写高质量JS代码上》今次整理一下javascript函数知识点。

2.使用函数

函数给程序员提供了主要的抽象功能,又提供实现机制。函数可以独立实现其他语言中的多个不同的特性,例如,过程、方法、构造函数,甚至类或模块。

2.1 理解函数调用、方法调用以及构造函数调用之间的不同

针对面向对象编程,函数、方法和类的构造函数是三种不同的概念。

使用模式:1,函数调用

function hello(username){
return "hello" + username;
}

2,方法调用

var obj = {
hello : function(){
return "hello , " + this.username;
},
username : "floraLam"
};
ohj.hello();//"hello , floraLam"

this变量被绑定到对象是由于hello方法被定义在obj对象中,我们也可以在另外一个对象中赋值一份相同的函数引用,并得到相同的答案。

var obj2 = {
hello : obj.hello(),
username : "floraLam"
};

3,构造函数使用

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

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

var u  = new User("floraLam","123");

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

2.2 熟练掌握高阶函数

高阶函数无非是那些将函数作为参数或返回值的函数,将函数作为参数(通常称为回调函数,因为高阶函数"随后调用"它)是一种特别强大、富有表现力的惯用法,也在js程序中被大量使用。

考虑数组的标准sort方法,为了对所有数组都能工作,sort方法需要调用者决定如何比较数组中的任意两个元素。

function compareNumber(x,y){
if(x < y){
return -1;
}
if(x > y){
return 1;
}
return 0;
}
[3,1,4,1,5,9].sort(compareNumbers);//[1,1,3,4,5,9]
[3,1,4,1,5,9].sort(function(x,y){
if(x < y){
return -1;
}
if(x > y){
return 1;
}
return 0;
});//[1,1,3,4,5,9]

上述例子使用一个匿名函数进一步简化。

学会使用高阶函数通常可以简化代码并消除繁琐的样板代码。简单的转换字符串数组的操作我们可以使用循环这样实现:

var names = ["Fred","Wilma","Pebbles"];
var upper = [];
for(var i = 0,n = names.length ;i< n;i++){
upper[i] = names[i].toUpperCase();
}
upper;//["FRED","WILMA","PEBBLES"];

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

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

另外,例如我们想创建若干个方法创建不同的字符串,具有共同的实现逻辑,每个循环通过连接每个独立部分的计算结果来创建一个字符串。

 function bulidString(n,callback){
var result = "";
for(var i = 0 ; i < n ;i++){
result += callback(i);
}
return result;
} var alphabet = bulidString(26,function(i){
return String.fromCharCode(aIndex + i);
});
alphabet;//"abcdefghijklmnopqrxtuvwxyz"; var digits = buildString(10,function(i){ return i;})
digits;//"0123456789" var random = buildString(9,function(){
random += String.fromCharCode(Math.floor(Math.random()*26)+aIndex
});
random;//"yefjmcef"(随机)

这样能够使得读者更清晰了解该代码能做什么,无须深入实现细节。

备注

   javascript返回指定范围的随机数(m-n之间)的公式:Math.random()*(n-m)+m

  同时要注意题目要求,是否要求返回正整数

2.3调用模式

调用一个函数将会暂停当前函数的执行,传递控制权与参数给新的函数。 除了声明时定义的形式参数,每个函数会接收到两个新的附加参数:this和arguments。

this是个很重要的参数,并且它的值是由调用模式决定的。

以下是JavaScript中很重要的4个调用模式:

a. 方法调用模式the method invocation pattern

b. 函数调用模式the function invocation pattern

c. 构造器调用模式the constructor invocation pattern

d. Apply调用模式the apply invocation pattern

这些模式在如何初始化关键参数this上存在差异

1. 方法调用模式the method invocation method

当函数作为对象的方法的时候,我们就叫函数为方法。当一个方法被调用的时候,this绑定到调用的对象。

var myObj={

    val:0,
increment:function(inc){
this.val+=typeof inc ==="number"? inc:1;
},
get_val:function(){return this.val;} } myObj.increment();// 1 myObj["increment"](2);//3

小结:

1、通过this可取得它们所属对象的上下文的方法称为公共方法

2、当用 .或者下标表达式 来使用一个函数的时候,就是方法调用模式,this对象绑定到前面的对象。

3,一个函数可以使用this来访问对象,所以它能检索对象的值或者更改对象的值。绑定this到对象发生在调用的时候。

2. 函数调用模式the function invocation pattern

当一个函数不是一个对象的属性,那么它就是作为函数来调用的。当一个函数作为函数调用模式来调用的时候,this绑定到全局对象。这是JavaScript设计时的错误并延续了下来。

function add(x,y){
  return x+y;
} myObj.double=function(){ var that=this;
var helper=function(){
that.val=add(that.value,that.value);
//错误的写法可能是这样,为什么错呢?因为函数作为内部函数调用的时候,this已经绑定到了错误的对象,全局对象并没有val属性,所以返回不正确的值。
//this.val = this.val+this.val;
  }
  helper();
} myObj.double();//6

3. 构造器调用模式the constructor invocation pattern

JavaScript是一门基于原型继承的语言,这意味着对象可以直接继承属性从其它的对象,该语言是无类别的。

如果在一个函数前面带上new来调用,那么将得到一个隐藏连接到该函数的prototype成员的新对象,同时this也将会绑定到该新对象。

new前缀也会改变return语句的行为。这也不是推荐的编程方式。

var Foo = function(status){
this.status = status;
} Foo.prototype.get_status = function(){
return this.status;
} //构造一个Foo实例 var myFoo = new Foo("bar");
myFoo.get_status();//"bar"

4. Apply调用模式the apply invocation pattern

因为JavaScript是一个函数式的面向对象语言,所以函数可以拥有方法。

Apply方法拥有两个参数,第一个是将绑定到this的值,第二个是参数数组,也就是说Apply方法让我们构建一个数组并用其去调用函数,即允许我们选择this的值,也允许我们选择数组的值。

var array = [3,4];

var sum = add.apply(null,array); // 7

var statusObj = {status:"ABCDEFG"};

Foo.prototype.pro_get_status = function(prefix){
return prefix +"-"+this.status;
} var status = Foo.prototype.get_status.apply(statusObj);// "ABCDEFG" var pro_status = Foo.prototype.get_status.apply(statusObj,["prefix"]);// "prefix -ABCDEFG"

通常情况下,函数或方法的接收者(级绑定到特殊关键字this的值)是由调用者的语法决定性的。特别地,方法调用语法将方法被查找对象绑定到this变量。然而,有时需要使用自定义接收者来调用函数。这时候就需要使用call方法或者bind方法自定义接收者来调用方法

2.4 使用bind方法提取具有确定接受者的方法

由于方法与值为函数的属性没有区别,因此也容易提取对象的方法并提取出函数作为回调函数直接传递给高阶函数。

但这也很容易忘记将提取出来的函数的接受着绑定到该函数被提取出的对象上。

var buffer = {
entries: [],
add :function(s){
this.entries.push(s);
}
}
var source = ["867","-","5309"];
source.forEach(butter.add);//error:entries is undefined

这个时候butter.add的接受者不是butter对象。函数的接收者取决于它是如何被调用的,forEach方法在全局作用域中被调用,因此forEach方法的实现使用全局对象作为默认的接收者,由于全局对象中没有entries属性,因此这段代码抛出错误。

forEach方法允许调用者提供一个可选的参数作为回调函数的接收者。

var source = ["867","-","5309"];
source.forEach(butter.add,butter);

但并非所有高阶函数都细心周到为使用者提供回调函数的接收者。

解决方法有两种:

1)创建一个显式地一buffer对象方法的方式调用add的封装函数。不管封装函数如何被调用,它总能确保将其参数推送到目标数组中。

var source = ["867","-","5309"];
source.forEach(function(s){
butter.add(s);
});

2)函数对象的bind方法需要一个接收者对象,并产生一个以该接收者对象的方法调用的方法调用原来的函数的封装函数。

var source = ["867","-","5309"];
source.forEach(butter.add.bind(buffer));

备注

  buffer.add.bind(buffer)创建一个新函数而不是修改buffer.add函数:

  buffer.add === buffer.add.bind(buffer); //false

编写高质量JS代码中的更多相关文章

  1. 编写高质量JS代码的68个有效方法(八)

    [20141227]编写高质量JS代码的68个有效方法(八) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  2. 编写高质量JS代码的68个有效方法(七)

    [20141220]编写高质量JS代码的68个有效方法(七) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  3. 编写高质量JS代码的68个有效方法(六)

    [20141213]编写高质量JS代码的68个有效方法(六) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  4. 编写高质量JS代码的68个有效方法(四)

    [20141129]编写高质量JS代码的68个有效方法(四) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  5. 编写高质量JS代码的68个有效方法(三)

    [20141030]编写高质量JS代码的68个有效方法(三) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  6. 编写高质量JS代码的68个有效方法(二)

    [20141011]编写高质量JS代码的68个有效方法(二) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  7. JavaScript手札:《编写高质量JS代码的68个有效方法》(一)(1~5)

    编写高质量JS代码的68个有效方法(一) *:first-child { margin-top: 0 !important; } body>*:last-child { margin-botto ...

  8. 编写高质量JS代码的68个有效方法(十三)

    No.61.不要阻塞I/O事件队列 Tips: 异步API使用回调函数来延缓处理代价高昂的操作以避免阻塞主应用程序 JavaScript并发的接收事件,但会使用一个事件队列按序地处理事件处理程序 在应 ...

  9. 编写高质量JS代码的68个有效方法(九)

    No.41.将原型视为实现细节 Tips: 对象是接口,原型是实现 避免检查你无法控制的对象的原型结构 避免检查实现在你无法控制的对象内部的属性 我们可以获取对象的属性值和调用其方法,这些操作都不是特 ...

随机推荐

  1. SQLSERVER 建立全文检索

    --  创建测试表 --  DROP TABLE FullTextIndexing CREATE TABLE FullTextIndexing ( ID INT IDENTITY(1,1) NOT N ...

  2. java-tip-HashMap

    HashMap的基本查找过程: 先使用key.hashCode()生成哈希值,根据哈希值来确定key存放的位置 找到key在数组中的位置后,再使用key.equals()方法来找到指定的key. 1. ...

  3. iOS 10 适配 ATS(app支持https通过App Store审核)

    iOS 10 适配 ATS 一. HTTPS 其实HTTPS从最终的数据解析的角度,与HTTP没有任何的区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据 ...

  4. mysql5.6新建的user为啥会看到没有授权过的db?

    数据库版本Server version: 5.6.22-log MySQL Community Server (GPL) root用户登录后,新建db,叫test2mysql> create d ...

  5. python用sqlite3模块操作sqlite数据库-乾颐堂

    SQLite是一个包含在C库中的轻量级数据库.它并不需要独立的维护进程,并且允许使用非标准变体(nonstandard variant)的SQL查询语句来访问数据库. 一些应用可是使用SQLite保存 ...

  6. combotree 满足条件的节点不可选中

    combotree: $("#Parent").treegrid("unselect");

  7. poj 2007 Scrambled Polygon

    #include<stdio.h> #include<algorithm> using namespace std; #define Max 60 struct Point { ...

  8. es学习-索引管理

    1.创建索引 http://localhost:9200/suoyinguanli211/ 参数: { "settings":{ "index":{ ,分片数 ...

  9. 对SOA架构思想的一些说明(转)

    出处:http://kb.cnblogs.com/page/510698/ 从纵向到横向 传统业务系统的构建更多的是竖井式的纵向思想,这个主要是从单个业务系统孤立来看都是垂直应用.那么SOA架构的视角 ...

  10. [GO]接口的定义和实现

    package main import "fmt" type Humaner interface { SayHi() } type Student struct { name st ...