[Effective JavaScript 笔记]第3章:使用函数--个人总结

时间:2022-09-06 15:36:01

前言

这一章把平时会用到,但不会深究的知识点,分开细化地讲解了。里面很多内容在高3等基础内容里,也有很多讲到。但由于本身书籍的篇幅较大,很容易忽视对应的小知识点。这章里的许多小提示都很有帮助,特别是在看对应内容的时候,把高3等工具书籍放在一边,边查边看收获很大。这章重点围绕函数的相关属性,方法,参数,关键字,命名,柯里化,高阶,闭包等内容作了各种提示。下面只是个人对于各条内容的一些总结,知识面有限,如有不对请大家一定指出。真心希望大家可以给点指导,个人写博客,感觉一直没人交流很没有动力的。

第18条:理解函数调用、方法调用及构造函数调用之间的不同

个人总结

这节里主要讲了函数在js中存在的3种形式,也是在不同的环境中的叫法不同而已。

函数调用

纯函数

就是一组输入产生一组输出,之间不对上下文环境中的其它变量产生副作用(这个词也是别处学到的,其实就是上下文中,有这个函数和没有这个函数对其它变量不会产生影响)。只是一个功能性的存在,类似于工具函数。

不纯的函数

对应的是对上下文环境会产生影响,并要去修改和访问上下文环境中变量的函数。这种函数也产生了很大的复杂性,对于没有块级作用域的js语言来说。这其中也就包含了一些对于闭包,作用域的技巧。对于初学者,这也会产生一些困扰。可参见之前《[Effective JavaScript 笔记] 第11条:熟练掌握闭包》。

方法调用

方法调用其实里面如果没有和this关键相关的操作,就和函数调用一样一样的。当包含this时,这个调用就相对复杂了,是方法就一定有对应的对象,这个对象就是this的指向值。难点主要是判断,什么时候this对应的对象是什么。这里也会牵扯到函数在运行时,各变量值的查找和访问的内容,也就是常听到的作用域链,而这个this也是在这个过程中才被确定的。
一组相关功能的函数放到一起,组成一个对象,这个对象只是一个简单的工具组合,里面的方法调用就可以等同于函数,对象只是用于组织而已,并不起到什么实质的作用,如Math对象,里面的方法都是一些数学函数。

构造函数

这个是开启js面向对象可能的一种应用形式,构造函数不同于函数调用和方法调用,它的调用过程需要一个关键字new,如果没有这个那就是函数调用,不会产生对象。使用new后一切都变了,new在调用构造函数,会产生一个新对象。调用几次产生几个,这些个对象就是函数中this的对应指向。这里先不说原型对象什么的,这个应该在下章会有。

提示

  • 方法调用将被查找方法属性的对象作为调用对象

  • 函数调用将全局对象(处于严格模式下则为undefined)作为其接收者。一般很少使用函数调用语法来调用方法

  • 构造函数需要通过new运算符调用,并产生一个新的对象作为其接收者。

第19条:熟练掌握高阶函数

个人总结

高阶函数,就是对函数的一种多重嵌套,可以把函数体作为参数或返回值。再结合闭包的相关特性,实现各种有用的功能。

作为返回值

今天看到一个试题,实现如下语法的功能:

var a = add(2)(3)(4); //9

这个就是一个高阶函数的应用,
分析:add(2)会返回一个函数,
add(2)(3)也会返回一个函数,
最后add(2)(3)(4)返回一个数值。
实现:

function add(num1){
return function(num2){
return function(num3){
return num1+num2+num3;
}
}
}
add(2)(3)(4);//9

这个没有错的,可以完美解决问题。
优化:这里只讨论关于高阶函数的部分,对于更好的解决方案,可以实现无限这种调用,代码如下:

//方法一
function add(a) {
var temp = function(b) {
return add(a + b);
}
temp.valueOf = temp.toString = function() {
return a;
};
return temp;
}
add(2)(3)(4)(5);//14
//方法二、另看到一种很飘逸的写法(来自Gaubee):
function add(num){
num += ~~add;
add.num = num;
return add;
}
add.valueOf = add.toString = function(){return add.num};
var a= add(3)(4)(5)(6); // 18
//方法二注释:其实就相当于,只不过对函数应用了自定义属性,用于存储值。
;(function(){
var sum=0;
function add(num){
sum+=num;
return add;
}
add.valueOf=add.toString=function(){return sum;}
window.add=add;
})()
var a= add(3)(4)(5)(6); // 18

以上结合了,类型的隐式转换,可以查看《[Effective JavaScript笔记]第3条:当心隐式的强制转换

作为参数

函数作为参数的高阶运用,在js的日常编程中可以说随处可见。
在DOM编程中,使用事件的时候。

window.addEventListener('click',function(){},false);

在使用jquery的事件的时候

$(function(){

})

在使用动画函数的时候

$().animate({},function(){});

在使用数组方法的时候

var a=[12,24,56,7,68,9,1];
a.sort(function(a,b){
return a-b;
})

上面的这些常用的场景,都是把函数作为参数的形式,传递给别一个函数。而这里的另一个函数,可以完成大部分的抽象工作,把常用的功能抽象出来为工具函数,把个性化的处理放到回调函数中。

提示

  • 高阶函数是那些将函数作为参数或返回值的函数

  • 熟悉掌握现有库中的高阶函数

  • 学会发现可以被高阶函数所取代的常见的编码模式

第20条:使用call方法自定义接收者来调用方法

个人总结

这个call方法和apply方法,作用是把函数绑定到相应的对象,也就是接收者。简单明了的说,就是定义函数中this的指向问题。它们接收的第一个参数,即为要绑定的对象;它们主要区别在第二个参数,call是一个个参数,apply是一个参数组成的数组。这种方法的优点之处,对方法或函数的重用,比如借用已经在其它对象上实现的方法。

function a(name){
this.name=name;
}
var obj={
info:function(name,age){
a.call(this,name);//函数的复用
this.age=age;
}
}
var obj2={}
obj.info.call(obj2,'cedrusweng',30);//方法重用
obj2.name;//"cedrusweng"
obj2.age;//30

提示

  • 使用call方法自定义接收者来调用函数

  • 使用call方法可以调用在给定的对象中不存在的方法

  • 使用call方法定义高阶函数允许使用者给回调函数指定接收者

第21条:使用apply方法通过不同数量的参数调用函数

个人总结

apply方法,第一个参数是函数的绑定接收者,如果没有可以传入null。第二个参数是参数数组。
没有什么其它特别的使用方法,对应这个可以用call方法进行替换。
应用:给可变参数的函数传值

function applyFn(){
for(var i=0,len=arguments.length;i<len;i++){
//somecode
}
}
applyFn.apply(null,[1,2,3,3,54,'6']);

提示

  • 使用apply方法指定一个可计算的参数数组来调用可变参数的函数

  • 使用apply方法的第一个参数给可变参数的方法提供一个接收者

第22条:使用arguments创建可变参数的函数

个人总结

arguments对象是一个类数组的对象。

类数组

就是有"0","1"等代表索引的键值,并含有length属性的对象。这里用类数组,意思是像数组但没有对应的数组方法,但可以通过转换复制出一个真正的数组。这里可以通过一些返回数组的方法,来对类数组进行复制。其中经常使用的代码如下(使用call方法)

var likeArr={
'0':'dddd',
'1':10,
a:1000,
b:400,
length:2
}
var slice=[].slice;
var actualArr=slice.call(likeArr);
actualArr;//['dddd',10]

使用上面的方法,可以把arguments对象处理成真正的数组,这是一个副本,对arguments对象的修改并不会反应到这个画本上,所以可以防止一些arguments对象被修改造成的问题。而且可以使用数组对象提供的高阶函数方法。

提示

  • 使用隐式的arguments对象实现可变参数的函数

  • 考虑对可变参数的函数提供一个额外的固定元素的版本,从而使使用者无需借助apply方法。

第23条:永远不要修改arguments对象

个人总结

arguments对象中的参数,是和形参一一对应的,对于arguments对象的修改会影响到实参的值。最终,函数的实际功能完全无法预料。在严格模式下,对arguments对象进行修改会直接导致错误。这个可以通过上条讲的,使用apply方法把arguments对象转化成一个真正的数组副本。从而避免对象修改对实际功能产生影响。下面是一个函数的应用

//实际目的是去除arguments的前两个参数,结果是obj,method两个形参数的值也被修改
function callMethod(obj,method){
var shift=[].shift;
shift.call(arguments);
shift.call(arguments);
return obj[method].apply(obj,arguments);
}
//复制arguments的一份副本,然后去除参数对象数组中的obj,method的值,实参并没有改变,只是改变了数组副本中的值
function callMethod(obj,method){
var args=[].slice.call(arguments);
args.shift();
args.shift();
return obj[method].apply(obj,args);
}
//通过slice方法直接去除前两个值,简化版
function callMethod(obj,method){
var args=[].slice.call(arguments,2);
return obj[method].apply(obj,args);
}

提示

  • 永远不要修改arguments对象

  • 使用[].slice.call(arguments)将arugments对象复制到一个真正的数组中再进行修改

第24条:使用变量保存arguments的引用

个人总结

arguments对象,只是保存着当前函数的参数信息。如果出现多层函数,内层函数是无法访问外层的arguments对象的,因为在它内部的arguments对象是它自身的参数信息。如果想访问到外部的arguments对象的相关信息,就要借助于一个新的变量,这个变量只是用于对象的一个引用,方便内部函数的访问。如下代码

function outFn(){
function innerFn(){
return arguments[0];//希望访问外部函数的第一个参数
} return innerFn;
}
var fn=outFn(100);
fn();//undefined function outFn(){
var outArgs=arguments;
function innerFn(){
return outArgs[0];//希望访问外部函数的第一个参数
} return innerFn;
}
var fn=outFn(100);
fn();//100

上面代码就是一个注意的例子,我平时很少直接使用arguments对象,都是给予对应的形参,方便配置说明。

提示

  • 当引用arguments时当心函数嵌套层级

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

第25条:使用bind方法提取具有确定接收者的方法

个人总结

bind方法是ES5才提供给函数对象的。其目的是把函数中this的指向明确化,防止this不明不白产生错误。这个方法会返回一个绑定了接收者的新函数。

//bind方法示例
function a(){
this.name='lizi';
}
var obj={};
var b=a.bind(obj);
b();
obj.name;//'lizi'
//不使用bind方法
function a(){
this.name='lizi2';
}
var obj={};
var b=function(){return a.call(obj)};
b();
obj.name;//'lizi2'

我觉得这个只是一种简化,其实像上面这种也是可以实现的。当你使用的js环境支持bind方法,那首先使用bind方法。
对于不支持bind方法的,可以使用下面的版本兼容方法

if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
} var aArgs = [].slice.call(arguments, 1),//获取绑定对象oThis后的预置参数
fToBind = this,//原函数体
fNOP = function() {},//空构造函数
fBound = function() {
return fToBind.apply(this instanceof fNOP? this: oThis,
aArgs.concat([].slice.call(arguments)));//返回的新函数,接收的参数
}; if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();//接上原型链 return fBound;
};
}

提示

  • 要注意,提取一个方法将方法的接收者绑定到该方法的对象上

  • 当给高阶函数传递对象方法时,使用匿名函数在适当的接收者上调用该方法

  • 使用bind方法创建绑定到适当接收者的函数

第26条:使用bind方法实现函数柯里化

个人总结

从上一条中bind方法的兼容实现就可以看出,其中可以先预置一些参数。只处理新函数传入的参数,产生的新函数,对参数进行了简化。这里看可以看出使用bind方法对函数进行柯里化很方便快捷。

1、可以直接看书上的例子

function simpleURL(protocol,domain,path){
return protocol+'://'+domain+'/'+path;
}
var paths=['wengxuesong/p/5545281.html#wxs-h-11','wengxuesong/p/5545281.html#wxs-h-10','wengxuesong/p/5545281.html#wxs-h-9'];
var urls=paths.map(function(path){
return simpleURL('http','cnblogs.com',path);
});

2、对函数进行柯里化

function simpleURL(protocol,domain,path){
return protocol+'://'+domain+'/'+path;
}
function pathURL(path){//对simpleURL进行了柯里化
return simpleURL('http','cnblogs.com',path);
}
var paths=['wengxuesong/p/5545281.html#wxs-h-11','wengxuesong/p/5545281.html#wxs-h-10','wengxuesong/p/5545281.html#wxs-h-9'];
var urls=paths.map(pathURL);

3、使用bind方法进行柯里化

function simpleURL(protocol,domain,path){
return protocol+'://'+domain+'/'+path;
}
var paths=['wengxuesong/p/5545281.html#wxs-h-11','wengxuesong/p/5545281.html#wxs-h-10','wengxuesong/p/5545281.html#wxs-h-9'];
var urls=paths.map(simpleURL.bind(null,'http','cnblogs.com'));

从上面代码可以看出,使用bind方法是这里处理最简洁的,对于2里的方法并没有什么错,使用原生支持的bind方法更快捷。(环境支持的情况下)

提示

  • 使用bind方法实现函数柯里化,即创建一个固定需求参数子集的委托函数

  • 传入null或undefined作为接收者的参数来实现函数的柯里化,从而忽略其接收者

第27条:使用闭包而不是字符串来封装代码

个人总结

使用字符串封装代码,这个可能也就是json数据的传输过程会用到。其它情况从来没有使用过,但使用闭包来对代码进行封装经常使用。首先,可以产生安全的作用域,可以避免命名冲突,可以对代码进行功能分块。字符串封装,相当于还需要解析的一个过程,在这个过程中,解析后的代码都是执行在全局作用域的,无法访问到闭包中的变量值。对字符串中的代码也不能有效的优化,编译无法一开始就对字符串中的代码进行优化。

提示

  • 当将字符串传递给eval函数以执行它们的API时,绝不要在字符串中包含局部变量引用

  • 接受函数调用的API优于使用eval函数执行字符串的API

第28条:不要信赖函数对象的toString方法

个人总结

1、toString方法功能很强大,函数对象的toString方法会返回函数体。
2、标准库中对于函数的toString方法没做任何规定,这意味着在不同的环境中可能产生不能的结果。
3、宿主环境提供的内置库提供的函数,无法使用toString获取函数体。
4、无法获取源代码中访问闭包中的值。
5、提取js函数源代码,可以借助于其它js解释器和处理库。

提示

  • 当调用函数的toString方法时,并没有要求js引擎能够精确地获取到函数的源代码

  • 由于不同的引擎下调用toString方法的结果可能不同,所以不要依赖于函数源代码的细节

  • toString方法的执行结果并不会暴露存储在闭包中的局部变量值

  • 通常情况下,应该避免使用函数对象的toString方法

第29条:避免使用非标准的栈检查属性

个人总结

这一条里对应的也就是arguments对象的两个属性:callee,caller。函数的一个函数caller。其中arugments.caller和function.caller是一个意思。arguments.caller已经不能使用,大多数浏览器厂商实现了function.caller,用于指向其调用函数。当使用严格模式时,这条讲的都不能使用,都会报错。所以能不用就别用,看到别人的代码里有这个就要提高警惕。然后看看为什么要用。

提示

  • 避免使用非标准的arguments.callee和arguments.caller属性,因为它们不具备良好的移植性

  • 避免使用非标准的函数对象caller属性,因为在包含全部栈信息方面,它是不可靠的

总结

这已经是第3章了,共29条,每天都想写2条相关的提示信息。这些内容看起来真的可以一眼带过,但当真的想把记录下来,并试着推导作者的代码及结果产生过程。这个就不是一下就可以完成的,有些知识和查一下资料,一些得翻翻书,一些得查查百度,这个过程很不错。一个知识点带起了很多内容,这些内容使我对这个知识点了解的更加深入。写博客不是一个非要展示给别人看的过程,而是自我知识的一下梳理过程。从开始写到现在很少有人和我交流相关内容。可能是知识太过浅显,不过于我却受益无穷。这个过程还会继续,坚持一下,终归要有始有终吧。

[Effective JavaScript 笔记]第3章:使用函数--个人总结的更多相关文章

  1. &lbrack;Effective JavaScript 笔记&rsqb;第7章:并发--个人总结

    前言 这一章的内容学到了事件队列和异步的API.js只是运行在其他应用程序的脚本语言.js即依赖于应用程序,也独立与应用程序.可以使它可以在多平台,多种环境上运行.ECMAScript标准中没有关于并 ...

  2. &lbrack;Effective JavaScript 笔记&rsqb;第2章:变量作用域--个人总结

    前言 第二章主要讲解各种变量作用域,通过这章的学习,接触到了很多之前没有接触过的东西,比如不经常用到的eval,命名函数表达式,with语句块等,下面是一个列表,我对各节的一点点个人总结,很多都是自己 ...

  3. &lbrack;Effective JavaScript 笔记&rsqb;第4章:对象和原型--个人总结

    前言 对象是js中的基本数据结构.对象在js语言编码中也随处可见,比如经常会用到的函数,也是一个Function构造函数,Function.prototype原型对象.每当声明一个函数时,都会继承Fu ...

  4. &lbrack;Effective JavaScript 笔记&rsqb;第5章:数组和字典--个人总结

    前言 这节里其实一直都在讨论对象这个在js中的万能的数据结构.对象可以表式为多种的形式,表示为字典和数组之间的区别.更多的我觉得这章讨论多的是一些对应实现功能的相关操作,有可能出现的bug以及如何避免 ...

  5. &lbrack;Effective JavaScript 笔记&rsqb;第6章:库和API设计--个人总结

    前言 又到了一章的总结,这章里的内容.是把我从一个代码的使用者,如何换位成一个代码的编写者.如何让别人用自己的代码更容易,不用去注意太多的无用细节,不用记住冗长的函数名.在使用API时怎样避免使用者会 ...

  6. &lbrack;Effective JavaScript 笔记&rsqb; 第1章:让自己习惯javascript小结

    在这里整理一下,每条对应的提示 第1条:了解使用的js版本 确定应用程序支持的js的版本(浏览器也是应用程序噢) 确保使用的js特性是应用程序支持的(要不写了也运行不了) 总是在严格模式下编写和测试代 ...

  7. &lbrack;Effective JavaScript 笔记&rsqb;第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  8. &lbrack;Effective JavaScript 笔记&rsqb; 第5条:避免对混合类型使用&equals;&equals;运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  9. &lbrack;Effective JavaScript 笔记&rsqb;第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

随机推荐

  1. solrconfig&period;xml和schema&period;xml说明

    1.   solrconfig.xml solrconfig.xml配置文件主要定义了SOLR的一些处理规则,包括索引数据的存放位置,更新,删除,查询的一些规则配置. 1.1.  datadir节点 ...

  2. myeclipse中java文件中文注释乱码问题

    在myeclipse中,有时打开java文件会发现中文注释全为乱码了,这个问题主要是因为编码的问题没有设置好,一个重要的原则就是保证所有的编码一致才不会发生乱码 出现乱码,需要知道三个地方的编码格式: ...

  3. redis缓存技术

    初学redis缓存技术,如果文章写得不好还请谅解 应用环境:win7 实现环境:cmd,eclipse redis缓存技术的特点就在于高效,因为目前涉及的数据量逐渐增多,在对于数据的存储上面和sql以 ...

  4. &lbrack;模板&rsqb;Min&lowbar;25筛

    用途 快速($O(\frac{n^{3/4}}{logn})$)地计算一些函数f的前缀和,以及(作为中间结果的)只计算质数的前缀和 一般要求f(p)是积性函数,$f(p)$是多项式的形式,且$f(p^ ...

  5. CentOS启动docker1&period;13失败&lpar;Job for docker&period;service failed because the control process exited with error code&period; See &quot&semi;systemctl status docker&period;service&quot&semi; and &quot&semi;journalctl -xe&quot&semi; for details&period;&rpar;

    一.启动失败 1.启动docker [root@localhost ~]# systemctl start docker Job for docker.service failed because t ...

  6. SoapUI并发模式

    soapUI支持test suite, test case级别的并发,合理使用这个功能,可以让自动化脚本短时间内跑完,为release省下时间. 1. 如何开启并发模式 图示,click projec ...

  7. OpenWrt实现802&period;11s组网模式

    参考 http://www.docin.com/p-277067204.html 无线网卡wlan0正常后,输入一下命令 iw dev wlan0 interface add mesh_iface t ...

  8. wget 用法

    wget -r -p -np -k http://xxx.com/xxx

  9. SQL&lowbar;server&lowbar;2008&lowbar;r2和visual studio 2010旗舰版的安装(2013-01-16-bd 写的日志迁移

    (以下操作是在Oracle VM virtualBox虚拟机中操作的,其实VMware Workstation 9虚拟机也挺不错的,不过用了很久的vmware想换个虚拟机用用 就暂时用Oracle V ...

  10. 2018&period;2&period;7 css 的一些方法盒子模型

    css 的一些方法 1.盒模型代码简写 盒模型的外边距(margin).内边距(padding)和边框(border)设置上下左右四个方向的边距是按照顺时针方向设置的:上右下左.具体应用在margin ...