学习js已经有一段时间了,大大小小还是能够做出一些东西来。不过觉得可惜的是,还是对js本身这门语言不是很熟悉,总有一点雾里看花的感觉,看得见,但是看不清楚。最近发现有一本关于js的叫做《忍者秘籍》的书刊,听说是jQuery作者写的,可以让初级者进阶,哇啦啦,打怪兽,加经验升级,多好的事情。于是,就有了此篇文章及其后续。闲话休谈,正事要紧!
javascript是一门函数式语言(functional language)。最重要的,在js中,函数是第一型对象(first-class object),也就是说,函数可以共处,可以将其视为其他任意类型的javascript对象。就像普通的js数据类型,函数可以被任意变量进行引用,或声明成对象字面量,甚至可以将其作为函数参数进行传递。
函数的第一型对象体现在:
1、它们可以通过字面量进行创建;
2、它们可以赋值给变量、数组或其他对象的属性;
3、它们可以作为参数传递给函数;
4、它们可以作为函数的返回值进行返回;
5、它们拥有动态创建并赋值的属性。
函数除了以上的功能外,它还包含一个功能,它们可以被调用。千万不要小瞧了函数的调用!因为不同的调用机制会产生不同的功效,特别是针对函数的this关键字。
当我们定义了一个函数,以便其他一些代码在适当的时候回头调用它,我们可以称之为回调。回调是高效利用js必不可少的一部分。
js函数是使用函数字面量(function literal)进行声明从而创建函数值的。例如:
function method(){}
一个函数的创建,由四个部分组成:
1、function 关键字;
2、函数名称,为可选。没有名称的函数称为匿名函数;
3、圆括号包含的一个以逗号分隔的参数列表,该参数列表可以为空,但是圆括号必须存在;
4、大括号包含的函数体。函数体可以为空,但大括号必须存在。
所有的函数都有一个name属性,该name属性值是该函数名称的字符串。没有名称的函数也有name属性,只是该属性值为空字符串。
//该函数的name属性值为Method function Method(){} //通过匿名函数赋值的情况,该函数的name属性值为空字符串,因为function关键字后面没有函数名称 var method=function(){}; //该函数的name属性值为method,因为该函数声明中,包含了函数名称 var fun=function method(){}
当我们声明一个函数时,我们不仅要关注该函数可用的作用域,还要关注该函数自身所创建的作用域,以及函数内部的声明是如何影响这些作用域的。
js不同于其他面向对象语言,在js中,作用域是由function进行声明的,而不是代码块。或许有的变量或函数声明于代码块,但不终结于该代码块。例如:
function method(value){ //虽然变量i在for代码块中声明,但是变量i的作用域离开了该for代码块也存在,一直到method函数体结尾,下面在if代码块中声明的变量a也是一个道理 for(var i=0;i<10;i++){ } alert(i);//10 alert(a);//undefined a=2; alert(a);//2 if(true){ var a=1; } alert(a);//1 }
关于变量和函数声明的作用域:
1、变量声明的作用域开始于声明的地方,结束于所在函数的结尾,与代码嵌套无关。值得注意的是,只要在该函数中,变量在其声明之前,还是可以通过对其赋值进行调用,如果不对其进行赋值,那么该变量值为undefined,列子如上面method函数中的变量a;
2、命名函数的作用域是指声明该函数的整个函数范围,与代码嵌套无关。说的就是函数声明提升机制。
3、对于作用域声明,全局上下文就像一个包含页面所有代码的超大型函数。
每个声明项的作用域不仅取决于它的声明,还取决于它是变量还是函数。
在编写js代码时,我们都是称调用js函数,但是,能否应该停下来想一想,函数被调用时,到底发生了什么?
事实证明,函数调用的方式对其函数内部的代码是如何执行的,有着巨大的影响,尤其是针对this参数,这是非常重要的。
实际上,有四种不同的方式可以进行函数调用,每种方式都有它们自己细微的差别:
1、作为一个普通函数进行调用,这是最简单的形式。如:method();
2、作为一个对象的方法进行调用,支持面向对象编程。如:person.setName();
3、作为构造器进行调用,创建一个新对象。如:new Person();
4、通过apply()或call()方法进行调用。如:[].slice.call(argument,0,3)。
在研究函数调用之前,我们先来看函数的参数传递。当一个参数列表作为函数调用的一部分时,这些参数会按照函数声明里的形参声明顺序,将参数值分别赋给这些形参。第一个参数赋值给第一个形参,第二个参数赋值给第二个形参,以此类推。
如果传入的参数与形参的个数不一致,怎么办?别担心,山人自有妙计:
1、如果实际传递的参数数量大于函数声明的形参数量,超出的参数则不会配给形参;
2、如果实际传递的参数数量小于形参数量,则没有对应参数的形参会被赋值为undefined。
在参数传递的时候,有一个非常有趣且非常有用的现象,所有的函数调用都会传递两个隐式参数:arguments和this。所谓的隐式,也就意味着两个参数不会显示在函数签名里,但是它们默默地传递给函数并存在于函数的作用域内。在函数内部,它们可以像其他显示命名的参数一样使用。
arguments参数:这是传递给函数的所有参数的一个集合。该集合拥有length属性,length值为全部参数的个数。我们可以通过for循环遍历出每个参数值,也可以像访问数组一样通过索引去获取其中的某个参数值。例如argument[0]就是获取第一个参数值。在这里,请注意,arguments不是数组!arguments不是数组!arguments不是数组!重要的事情说三遍。它就像一个类数组结构。
this参数:一个函数被调用时,除了传入函数的显示参数外,名为this的隐式参数也被传入了函数。this参数引用了与该函数调用进行隐式关联的一个对象,被称之为函数上下文(function context)。请注意,js中的this依赖于函数的调用方式。
如果一个函数作为普通函数进行调用,也就是例如method()这种,一个函数名称加上圆括号的样式。以这种方式进行调用时,函数的上下文是全局上下文,也就是说隐式传入的this是window对象。
如果将一个函数作为对象的方法进行调用,例如person.setName()这种,则该person对象就变成了setName函数的上下文。也就是说一个方法所属的对象在该方法体内可以以this的形式进行引用。说得通俗一点,传入该对象方法的this是当前调用该方法的对象,也就是person。
注意:相同的函数,其函数上下文(this)会随着函数调用方式的变化而变化,而不是取决于函数是怎样声明的。因为一个函数,可以被当做普通函数调用,也可以赋值给一个对象的方法。
将函数作为一个构造器进行调用,其实它并没有什么特别的地方。只是,构造器函数的声明和其他函数有点不大一样,不同的地方是在于如何调用该函数。将函数作为构造器(constructor)进行调用,我们要在函数名称前使用new关键字,例如:new Person()。
将函数作为构造器进行调用,是js的一个超级特性,因为构造器调用时,会发生如下行为:
1、创建一个新的空对象;
2、将该新的空对象当做this参数传递给构造器函数,从而成为该构造器的函数上下文(this);
3、如果该构造器函数没有显示的返回值,则新创建的对象就会作为构造器的返回值进行返回。
请注意:构造器的目的是要创建一个新对象并对其进行设置,然后将其作为构造器的返回值进行返回。任何干扰或者不存在这种意图的函数,都不适合作为构造器函数。
我们可以用以下代码来测试:
//声明一个构造器函数 function Person(){ this.showSelf=function(){return this;}; } //创建一个对象 var p=new Person(); if(p===p.showSelf()) alert("OK!"); else alert("Fail!"); //OK! //解析以上代码 //首先声明一个构造器函数Person //接着,用new调用构造器函数Person,并创建一个对象p //当构造器函数Person被调用时: //1.创建一个新的空对象 //2.将新创建的对象当做this参数隐式传递给构造器函数Person。然后在Person内部,this新增了一个方法showSelf //3.将该新创建的对象当做返回值,并赋值给p变量//p对象和p.showSelf方法返回的this指向的是同一个对象,因此它们真相等!
由于构造器的编码和使用方式不同于其他函数,除了作为构造器调用之外,通常它们也不是全部都那么有用,因此命名约定的出现是为了区别构造器和其他普通函数的方法。一般函数的命名以动词开头,比如setName、getName之类的。而构造器的名称通常是由一个描述所构造对象的名词来命名,如Person。
在函数调用的时候,js为我们提供了一种方式,就是可以显示指定任何一个对象作为其函数上下文。js中的每个函数都有apply()和call()方法。使用其中一个方法,我们都可以实现这种功能。有的同学会迷惑,为啥函数也有它们自己的方法呢?因为作为第一型对象(透个底,它是由Function()构造器创建的),函数可以像其他任何类型的对象一样,拥有属性和方法。
通过函数的apply()方法来调用函数,我们要给apply()传递两个参数:第一个是函数上下文,也就是this参数,另外一个是作为函数参数所组成的数组。call()方法与此非常类似,唯一不同的就是,给函数传入的参数是一个参数列表,而不是单个数组。如以下代码所示:
function sum(){ var result=0; for(var i=0;i<arguments.length;i++) result+=arguments[i]; this.result=result; } var obj1={},obj2={}; sum.call(obj1,1,2,3,4); sum.call(obj2,5,6); alert(obj1.result);//10 alert(obj2.result);//11 //其实函数调用apply或call方法,就是显示指定传给函数的this和arguments参数
使用apply()和call()方法,我们可以选择任意对象作为函数的上下文(this)。
我们可以通过apply()或call()方法,给回调函数强制指定函数上下文,也就是this参数。如下代码所示:
//给callback强制指定其this参数为list数组的每个迭代的元素,arguments为当前迭代元素的索引 function forEach(list,callback){ for(var i=0;i<list.length;i++) callback.call(list[i],i); } var names=['小明','小花','小萌']; //回调函数的this参数就是names数组的单个元素 forEach(names,function(index){ console.log("大家好,我是"+this); }); //大家好,我是小明 //大家好,我是小花 //大家好,我是小萌
来源:https://www.cnblogs.com/williamwsj/p/7155674.html