资讯专栏INFORMATION COLUMN

js函数探索

thursday / 977人阅读

摘要:关于构造函数有几点需要特别注意构造函数允许在运行时动态的创建并编译函数。而函数本身的表示该函数的形参。每一个函数都包含不同的原型对象,当将函数用作构造函数的时候,新创建的对象会从原型对象上继承属性。

该文章以收录: 《JavaScript深入探索之路》 前言

函数是这样的一段JavaScript代码,它只定义一次,但是可能被执行或调用任意次。你可能已经从诸如子例程或者过程这些名字里对函数的概念有所了解。 JavaScript函数时参数化的:函数的定义会包括一个称为形参的表示符列表,这些参数在函数体中像局部变量一样工作。函数调用会为形参提供实参的值,函数使用它们实参的值来计算返回值,成为该函数调用表达式的值。除了实参外,每次调用还会拥有另一个值 —— 本次调用的上下文 —— 这就是this关键字的值。

参数有形参(parameter)和实参(argument)的区别,形参相当于函数中定义的变量,实参是运行时的函数调用时传入的参数。

创建函数

一般我们有三种方法去创建函数:

函数声明:function foo(){}

函数表达式:var foo = function(){}

函数构造法:var foo = new Function("a","b","return a+b")

函数声明和函数表达式

1.什么是函数声明,什么是函数表达式

ECMA规范说:函数声明必须带有标示符(Identifier)(就是大家常说的函数名称),而函数表达式则可以省略这个标示符:

// 函数声明
function 函数名(可选参数){}

// 函数表达式
function 可选函数名(可选参数){}

我们可以很清楚的看出,如果没有标识符(函数名),该函数就一定是函数表达式,如果有标识符我们该怎么区分函数声明和函数表达式呢。

函数声明我们不用太多的考虑,很容易看出来,我们来看看函数表达式都有哪些自然就知道哪些是函数声明了。

第一种函数表达式:var声明的函数我们很常见

// 没有标识符一定是表达式
var fun = function(){}

//我们还可以这样写 

var fun = function foo(){}

第二种我们带有标识符,但将函数赋值给变量后,该函数就成了函数表达式。在这里foo标识符在该函数体外访问时并不起作用

var fun = function foo(a){
  
    if(a){
      console.log("外部调用了");  
      foo(false); //这会执行函数fun。
    }else{
      console.log("内部调用了");
    }

}

fun(true); //外部调用了 ,内部调用了

foo(); // 报错 ReferenceError: foo is not defined

我们可以看出,函数表达式中foo标识符只能在函数内部使用,去访问该函数,在函数外部访问不到任何东西。上面我们加判断是为了防止函数一直递归。

第二种函数表达式:()括号包住的函数

// 这是函数表达式
(function foo(){})

括号包住的函数是函数表达式是因为,()是一个分组操作符,分组操作符内只能是表达式。

例如:

//Unexpected token var
(var a = 0) 

上面报错,这是因为var是一个语句,而分组操作符内只能是表达式。还有我们在使用evel()时这样写 var ev = eval("(" + json + ")") ,这里是强制将{}解析为表达式而不是代码块。

2.函数声明和函数表达式的特点

函数声明在我们的代码执行前,会被整个提升,也就是说我们在函数声明之前就可以调用该函数,而函数表达式不能够被提升。

a() // 结果 我是函数a"
function a(){
    console.log("我是函数a");
}

fun() //结果 fun is not a function
var fun = function b(){
    console.log("我是函数b");
}

为什么会出现这种情况我们在以后的文章中会详细的讨论。同时,我们应该养成一种习惯,尽量不要在函数声明之前使用函数。这只是一种良好的代码书写习惯。

其次我们需要注意的是在if判断语句中我们不应该出现函数声明,即便是没有报错,也是不推荐这样去写的。

立即执行函数(表达式)和自执行函数

1. 立即执行函数(表达式)

我们来看一下:

(function (){
    console.log("one");
})();


(function (){
    console.log("two");

}());

上边两个函数都可以自己去执行,而值有个共同的特点就是括号左边的函数必须是一个函数表达式,而不是函数声明。

// 如果括号左边是函数声明,报错
function fun(){
    console.log()
}()



// 括号左边是函数时表达式,(函数表达式都可以自执行)

var F = function(){
    return 10;
}();

true && function (){}();

0 , function (){}();

//你甚至看还可以这样

!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

new function(){}();

其实上边的些自执行函数表达式我们为了方便我们的阅读我们一般会用到第一个和第二个,当然如果你非要搞个性化,使用其他的也是可以。只不过你的代码并不美观,后期也不容易维护。

2. 什么是自执行函数,像这样

function fun1(){
    //code
}
fun1();


function fun2(){
    fun2() // 递归
}

// 这是一个自执行匿名函数
var foo = function () { arguments.callee(); };

看了这些例子,我们应该清楚了什么是自执行,什么是立即调用。

函数构造器

Function()构造函数并不需要通过传入实参以指定函数名,就像函数直接量一样,Function()构造函数创建一个匿名函数。

关于Function()构造函数有几点需要特别注意

Function()构造函数允许JavaScript在运行时动态的创建并编译函数。

Function()构造函数每次执行时都会解析函数主体,并创建一个新的函数对象,所以当在一个循环或频繁执行的函数中调用Function()构造函数效率是非常低的。而函数字面量(函数表达式)却不是每次遇到都会重新编译的,用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行

它所创建的函数并不是使用词法作用域,相反,函数体代码的编译总是会在顶层函数执行,

用函数构造器创建的函数不会在上下文中创建闭包,它们总是被创建在全局作用域中,当执行被创建的函数时,它们只能使用自己的局部变量或者全局变量。例如下面:

var scop = "global";
function fun(){
    var scop  = "fun";
    return new Function("return scope") // global
}
函数的属性、方法和构造函数

1. length属性

函数体中,arguments.length表示传入函数的实参个数。而函数本身的length表示该函数的形参。

 function fun(x,y,z){
     console.log(arguments.length);// 2
     console.log(arguments.callee.length); //3
 }

 fun(1,2);

我们这里提一下,在函数中的arguments,函数中arguments包含所用实参,它并不是一个真正的数组,而是一个实参的对象。另外他还有两个特别的属性calleecaller属性。

callee属性只带党庆正在执行的函数。
caller属性时非标准的,但大多数浏览器都实现了这个属性它指带调用当前正在执行的函数的函数。

通过caller属性可以访问调用栈。callee属性在某些时候会非常的有用,比如在匿名函数中通过callee来递归调用自身,上面我们已经提到过。

2. prototype属性

每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称作"原型对象"。每一个函数都包含不同的原型对象,当将函数用作构造函数的时候,新创建的对象会从原型对象上继承属性。

3. call()方法和apply()方法

call 和 apply 是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。因为 JavaScript 的函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。

它们的用法也是很简单的。例如:

var obj = {
        msg:"我是obj对象",
    fun:function(){
       console.log(this.msg);
    },
}

var objTwo = {
    msg:"我是objTwo对象"
};

// 这里我们想让通过objTwo 对象调用 obj对象的fun方法,
// 此时obj的fun方法中的this已经改变了指向。
obj.fun.call(objTwo) // 我是objTwo对象

call 和 apply 的区别在于他们传参的形式不同:

var obj = {
        msg:"我是obj对象",
    fun:function(one,two){
       console.log(this.msg);
       console.log("我是参数:"+ one + "," +two);
    },
}

var objTwo = {
    msg:"我是objTwo对象"
};

// 这里我们想让通过objTwo 对象调用 obj对象的fun方法,
// 此时obj的fun方法中的this已经改变了指向。
obj.fun.call(objTwo,1,2) // 我是objTwo对象 ,我是参数:1,2

obj.fun.apply(objTwo,[1,2]) // 我是objTwo对象 ,我是参数:1,2

高阶函数

所谓高阶函数就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数。我们来看一个例子:

这个例子的主要作用是,fun函数接收f()g()两个函数,并返回一个新的函数用以计算f(g())

function fun(f,g){
    return function(){
      return f.call(this,g.apply(this,arguments));
    }
}

var square = function(x){
    return x * x
}

var sum = function(x,y){
    return x + y;
}

var suqareOfSum = compose(square,sum)

squareOfSum(2,3) // 25

在这里函数fun() 就是高阶函数。

不完全函数

不完全函数是一种函数变换技巧,即把一次完整的函数调用折成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数,每次函数调用叫做不完全调用,这种函数变换的特点是每次调用都反一个函数,知道得到最终运行结果为止,例如:将函数f(1,2,3,4,5)的调用修改为等价的f(1,2)(3,4)(5)后者包含三次调用,和每次调用相关的函数就是 不完全函数”。

结束

参考文献: 《JavaScript权威指南》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89706.html

相关文章

  • javascript对象不完全探索记录03:小伙子,你对象咋来的?上篇

    摘要:看着别人写的功能对,就直接拿过来用,写出来的代码臃肿到爆炸,满屏幕的的初级使用方式,运气好了能凑合跑起来,出了问题全脸懵逼,心中安慰自己一万遍我可是干设计的,但是既然打算好好整下就得从头开始看了。 温馨提示:作者的爬坑记录,对你等大神完全没有价值,别在我这浪费生命 首先,说实话,我对不起javascript,作为一个接触前端快10年的老前端(伪),一直发扬的是不求甚解的拿来就用主义。看...

    Pluser 评论0 收藏0
  • React原理探索- @providesModule 模块系统

    摘要:原理探索模块系统是什么抛出组件化的概念后,对于开发者而言,为了提高代码的可读性与结构性,通过文件目录结构去阐述组件嵌套关系无疑是一个很好的办法,但是目录级别的加深,同时让的文件路径让人头疼。 React原理探索- @providesModule 模块系统 @providesModule是什么 react抛出组件化的概念后,对于开发者而言,为了提高代码的可读性与结构性,通过文件目录结构去...

    My_Oh_My 评论0 收藏0
  • javascript闭包不完全探索记录02:闭包?干嘛使!

    摘要:温馨提示作者的爬坑记录,对你等大神完全没有价值,别在我这浪费生命温馨提示续本文将会成为一篇笔记类型的文章,记录闭包具体的应用方式温馨提示再续本文存在错误,会慢慢改进的,请不要把我说的当真在上一篇博文闭包不完全探索记录闭包啥馅的中,对中 温馨提示:作者的爬坑记录,对你等大神完全没有价值,别在我这浪费生命温馨提示-续:本文(maybe)将会成为一篇笔记类型的文章,记录闭包具体的应用方式温馨...

    Render 评论0 收藏0
  • JavaScript模块化编程探索

    摘要:模块化编程,已经成为一个迫切的需求。随着网站功能逐渐丰富,网页中的也变得越来越复杂和臃肿,原有通过标签来导入一个个的文件这种方式已经不能满足现在互联网开发模式,我们需要团队协作模块复用单元测试等等一系列复杂的需求。 随着网站逐渐变成互联网应用程序,嵌入网页的Javascript代码越来越庞大,越来越复杂。网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发...

    jayzou 评论0 收藏0
  • 探索Javascript 异步编程

    摘要:因为浏览器环境里是单线程的,所以异步编程在前端领域尤为重要。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案函数体内外的数据交换和错误处理机制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我们日常编码中,需要异步的场景很多,比如读取文件内容、获取远程数据、发送数据到服务端等。因为浏览器环境里Javascript是单线程的,...

    Salamander 评论0 收藏0

发表评论

0条评论

thursday

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<