We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
函数定义有两种方式:一是函数的声明,二是函数的表达式。
//函数的声明 function functionName(arg0, arg1, arg2) { //函数体 } //函数的表达式 var functionName = function() { //函数体 }
对于函数的声明:有一个重要的特征那就是函数声明提升,其意思是在执行代码之前会读取函数的声明,这就意味着可以把函数声明放在调用它的语句的后面。
//函数的调用 sayHi(); //输出的是hello function sayHi(){ console.log("hello"); }
函数的表达式:这种形式看起来好像是变量赋值语句,即创建一个函数并将它赋值给变量functionName,在这种情况下创建的函数,叫匿名函数。因为function关键字后面没有标识符。函数表达式跟其他表达式一样,必须在使用它之前赋值。不然会报错。
//函数的调用,会报错,提示函数未定义 sayHi(); var sayHi = function(){ console.log("hello"); }
function sayHi(){ console.log("hello" + arguments[0] + "," + arguments[1] ); }
所以在js中函数的命名参数只是提供了便利,但是并不是必须的。
function doAdd(num1, num2) { if(arguments.length ==1) { console.log(num1+10); }else if(arguments.length == 2){ console.log(arguments[0]+num2); } } //调用函数 doAdd(1, 2);
function doAdd(num1, num2) { arguments[1] = 10; console.log(arguments[1]+num2); } //调用函数 doAdd(1, 2); //返回的是20,
我们对上面这个分析一下:
对于严格模式中,对arguments对象做了一些限制
函数中所有参数都是按值传递:这句话意思是说把函数外部的值复制给函数内部参数。就是把值从一个变量复制到另一个变量一样。
基本类型值的传递如同基本类型变量复制一样,而引用类型值的传递,则如果引用类型变量的复制一样。访问变量有按值和按引用两种方式,而参数只能按值传递。
看一个基本类型例子:
//基本类型 var addTen(num){ num+=10; return num; } var count =20; var result = addTen(count); console.log(result); //返回的是30 console.log(count); //返回的是20,没有改变
再看一个引用类型例子
//引用类型 function setName(obj){ obj.name = "jerry"; obj = new Object(); obj.name = "gery"; } var person = new Object(); setName(person); console.log(person.name); //返回的是jerry
基本类型很好理解,但是对用引用类型大家会疑惑,总是弄不清楚。我们可以对其分析一下
function addSomeNumber(num) { return num+100; } function addSomeNumber(num) { return num+200; } var result = addSomeNumber(100) //结果是300
第二个函数覆盖第二个函数。
在定义函数的时候,不必指定是否有返回值,实际上,任何函数在任何时候都可以通过return语句后跟要返回的值来实现返回值。
function sum(num1, num2) { return num1+num2; }
在严格模式对函数有一些限制
递归函数是一个函数通过名字调用自身的情况下构成。
function factorial(num) { if(num <=1) { return 1; }else { return num*factorial(num-1); } } //这上面一个是一个经典的递归函数,看上去并没有问题,但是如果这样的话 var factorial1 = factorial; factorial =null; console.log(factorial1(4)); //会报错
我们对上面这个进行分析一下,为什么会出现这样,是因为函数保存在变量factorial1中,再对这个函数赋值为null,因为这个是引用类型,所以这个是函数也是null,调用这个函数已经不存在,所以会报错。
对于上面这种情况,我们可以采用下面两种方式来解决
第一种:利用arguments.callee来实现,arguments.callee是代表一个正在执行的函数指针。因此可以用来实现对函数的递归调用---这个在严格模式下面会出错,因为严格模式下,不能通过脚本访问arguments.callee。
function factorial(num) { if(num <=1) { return 1; }else { return num*arguments.callee(num-1); } } var factorial1 = factorial; factorial =null; console.log(factorial1(4)); //这样就不会报错了
第二种:利用函数的表达式
function factorial = (function f(num){ if(num <=1) { return 1; }else { return num*f(num-1); } }); var factorial1 = factorial; factorial = null; console.log(factorial1(4)); //这样就不会报错了
在JavaScript中并没有块级作用域的概念,这就意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
function outputNumbers(count){ for(var i=0; i<count; i++) { console.log(i); } console.log(i); }
我们分析一下这个:
即使错误的重新声明一个变量同一个变量,也不会改变它的值。
function outputNumbers(count){ for(var i=0; i<count; i++) { console.log(i); } var i; //重新声明同一个变量 console.log(i); }
JavaScript从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。对于这个问题,可以用匿名函数来模范块级作用域。
闭包:是有权访问另一个函数作用域中的变量的函数。 常见创建闭包的方式有:在一个函数内部创建另一个函数 (闭包的相关知识在作用域的那一节有了详细的介绍,不在介绍)
作用域的这种配置机制引出一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值,因为闭包所保存是整个变量对象,而不是某个特殊的变量。下面我们看个例子:
//创建了一个函数 function createFunction() { //创建了一个数组 var result = new Array(); //一个for循环 for(var i=0; i<10; i++) { //一个闭包 result[i] = function(){ //访问的是其包含函数的变量 return i; } } }
返回的结果是一个数组,但是这个数组并不是[0,1..9] ,而是[10, 10..10],在每一个匿名函数的作用域链中都保存着createFunction()函数的活动对象,所以他们引用的是同一个变量i,所以当createFunction()函数返回后,变量i变成10,此时每一个匿名函数都引用着保存变量i的同一个变量对象。所以每个函数内部i的值都是10。
但是我们可以通过创建另一个匿名函数强制让闭包的行为符合预期
function createFunction() { var result = new Array(); for(var i=0; i<10; i++) { result[i] = function(num){ return function(){ return num; } }(i); } return result; }
在这里,我们并没有直接把闭包赋值给数组,而是定义了一个匿名函数。并将立即执行该匿名函数的结果赋值给数组。
这里的匿名函数有一个参数num。也就是最终的函数要返回的值,在调用每个匿名函数时,我们传入了变量i,由于函数的参数是按值传递,所以就会将变量i的当前值复制给参数num,而在这个匿名函数内部,又创建并返回一个访问num的闭包,这样以来,result数组中的每一个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值。
//块级作用域语法 (function(){ //这里是块级作用域 })();
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。可能这种语法有点不好理解。我们可以这样理解:
var count=5; outputNumber(count); //为了让代码更简洁 outputNumber(5); //我们这样写,是因为变量只不过值的另一种表现形式。因此可以用实际值代替变量 var someFuncton = function(){ //这里是块级作用域 }; someFuncton(); //根据上面的思路,可以这样写 //实际值代替变量 function(){ //这里是块级作用域 }(); //但是,function是关键字是当作一个函数的声明的开始,函数声明后面不能跟圆括号。 //而函数的表达式后面可以跟圆括号,把函数声明转为函数表达式,只要像下面这样 (function(){ //这里是块级作用域 })();
无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。例如:
function outputNumber(count) { (function(){ for(var i=0; i<count; i++){ console.log(i); } })(); console.log(i) //会出错的。 }
因为在for循环外部插入了一个私有作用域,在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。
在大型应用程序中,我们都应该尽量少向全局作用域添加变量和函数,因为过多的全局变量和函数,很容易导致命名冲突。通过创建私有作用域,每个开发人员可以使用自己的变量,又不必担心搞乱全局。 且这种做法可以减少闭包占用的内存问题。因为没有匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
严格来说,在javascript中没有私有成员的概念,所有对象的属性都是共有的,但是有一个私有变量的概念:任何函数中定义的变量,都可以认为是私有变量,因此不能在函数的外部访问这些变量。
私有变量:包括函数的参数,局部变量和函数内部定义的其他函数。
function add(num1, num2) { var sum = num1 + num2; return sum; }
在函数的内部,我们定义了三个变量 sum, num1, num2。 在函数内部可以访问这几个变量,但是在函数的外部则不能访问他们。如果在函数内部建一个闭包,闭包通过自己的作用域链也可以访问到这些变量。我们可以利用这种,创建用于访问私有变量的共有方法。
我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方法。
第一种:在构造函数中定义特权方法
function MyObject(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //特权方法 this.publicMethod = function(){ privateVariable++; return privateFunction(); } }
对这个例子而言, privateVariable和privateFunction()只能通过特权方法publicMethod来访问。在创建 MyObject的实例后,除了使用publicMethod()方法这一个途径外,没有任何方法可以直接访问privateVariable和privateFunction()
利用私有和特有成员,我们可以隐藏那些不应该被直接修改的数据。例如:
function Person(name) { this.getName = function(){ return name; }; this.setName = function(value){ name = value; }; } var person = new Person("jerry"); console.log(person.getName()); //返回jerry person.setName("gery"); console.log(person.getName()); //返回gery
以上代码定义了两个特权方法,getName()和setName(),这两个函数在构造函数外部也可以访问,而且有权访问私有变量name.但在构造函数的外部,没有任何办法访问name。
这两个方法是在构造函数内部定义的,他们作为闭包能够通过作用域链访问name,私有变量name在Person的每一个实例都是不同的,每次调用这个构造函数,都会重新创建这两个方法。
利用构造函数定义特权方法,有一个缺点,那就是必须使用构造函数,而构造函数模式针对每一组实例,都会创建同样一组新方法。
通过在私有作用域中定义私有变量和函数,同样可以创建特权方法。
(function(){ //私有变量和私有函数 var privateVariable = 10; function privateFunction(){ return false; } //构造函数(注意这个是全局变量) MyObject = function(){ }; //特权方法 MyObject.prototype.publicMethod = function(){ privateVariable++; return privateFunction(); } })();
这个模式创建一个私有作用域,并在其中封装了一个构造函数及相应的方法。我们把公有方法定义在原型上面,这里体现了原型模式,我们创建了函数的表达式,并没有创建函数声明,因为函数声明只能创建局部变量,,而且出于同样的原因,我们也没有在声明MyObject时,使用var关键字。
记住:初始化未经声明的变量,总会创建一个全局变量,所以MyObject是一个全局变量,但是,我们在严格模式下,给未经声明的变量赋值会报错的。
这个方法与刚才在构造函数中定义特权方法的区别在于:私有变量个函数是共享的。由于特权方法在原型上定义的,因此所有实例都使用同一个函数,而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。
模块模式:为单例创建私有变量和特权方法。 单例:指的是只有一个实例的对象。按照惯例,javascript中以对象字面量的方式来创建单例对象。
var singleton = { name : value, method : function(){ //这里是方法 } }; //模块模式通过为单例添加私有变量和特权方法,能够使其得到增强。 var singleton =function(){ //添加私有变量和函数 var privateVariable = 10; function privateFunction() { return false; } //特权方法 return { publicProperty : true, publicMethod : function(){ privateVariable; privateFunction(); } }; }();
这个模块模式使用了一个返回对象的匿名函数,在这个匿名函数,首先定义了私有变量和函数,然后将一个对象字面量作为函数返回。返回的对象字面量只包含可以公开的属性和方法,由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。
从本质上来讲:这个对象字面量定义的是单例的公共接口。这种模式在需要对单例初始化,同时又维护其私有变量时,是非常有用的。
如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。以这种模式创建的每一个单例都是Object的实例。因为最终要通过一个对象字面的方法来表示它。
这种增强的模块模式适合那些单例必须是某种类型的实例。同时还必须添加某些属性或方法对其加以增强的情况。
var singleton =function(){ //添加私有变量和函数 var privateVariable = 10; function privateFunction() { return false; } //创建对象 var object = new CustomType(); //特权方法 objectProperty : true, objectMethod : function(){ privateVariable; privateFunction(); //返回这个对象 return object; }();
The text was updated successfully, but these errors were encountered:
No branches or pull requests
函数的定义
函数定义有两种方式:一是函数的声明,二是函数的表达式。
对于函数的声明:有一个重要的特征那就是函数声明提升,其意思是在执行代码之前会读取函数的声明,这就意味着可以把函数声明放在调用它的语句的后面。
函数的表达式:这种形式看起来好像是变量赋值语句,即创建一个函数并将它赋值给变量functionName,在这种情况下创建的函数,叫匿名函数。因为function关键字后面没有标识符。函数表达式跟其他表达式一样,必须在使用它之前赋值。不然会报错。
函数的参数
arguments对象
所以在js中函数的命名参数只是提供了便利,但是并不是必须的。
命名参数可以与arguments对象一起使用
关于arguments行为,有一点要注意的那就是他的值永远与对应的命名参数的值保持同步。
我们对上面这个分析一下:
对于严格模式中,对arguments对象做了一些限制
函数中所有参数都是按值传递
函数中所有参数都是按值传递:这句话意思是说把函数外部的值复制给函数内部参数。就是把值从一个变量复制到另一个变量一样。
基本类型值的传递如同基本类型变量复制一样,而引用类型值的传递,则如果引用类型变量的复制一样。访问变量有按值和按引用两种方式,而参数只能按值传递。
看一个基本类型例子:
再看一个引用类型例子
基本类型很好理解,但是对用引用类型大家会疑惑,总是弄不清楚。我们可以对其分析一下
函数没有没有重载
第二个函数覆盖第二个函数。
函数返回值
在定义函数的时候,不必指定是否有返回值,实际上,任何函数在任何时候都可以通过return语句后跟要返回的值来实现返回值。
在严格模式对函数有一些限制
递归函数
递归函数是一个函数通过名字调用自身的情况下构成。
我们对上面这个进行分析一下,为什么会出现这样,是因为函数保存在变量factorial1中,再对这个函数赋值为null,因为这个是引用类型,所以这个是函数也是null,调用这个函数已经不存在,所以会报错。
对于上面这种情况,我们可以采用下面两种方式来解决
第一种:利用arguments.callee来实现,arguments.callee是代表一个正在执行的函数指针。因此可以用来实现对函数的递归调用---这个在严格模式下面会出错,因为严格模式下,不能通过脚本访问arguments.callee。
第二种:利用函数的表达式
模仿块级作用域
在JavaScript中并没有块级作用域的概念,这就意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。
我们分析一下这个:
即使错误的重新声明一个变量同一个变量,也不会改变它的值。
JavaScript从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。对于这个问题,可以用匿名函数来模范块级作用域。
闭包
闭包:是有权访问另一个函数作用域中的变量的函数。
常见创建闭包的方式有:在一个函数内部创建另一个函数
(闭包的相关知识在作用域的那一节有了详细的介绍,不在介绍)
闭包与变量
作用域的这种配置机制引出一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值,因为闭包所保存是整个变量对象,而不是某个特殊的变量。下面我们看个例子:
返回的结果是一个数组,但是这个数组并不是[0,1..9] ,而是[10, 10..10],在每一个匿名函数的作用域链中都保存着createFunction()函数的活动对象,所以他们引用的是同一个变量i,所以当createFunction()函数返回后,变量i变成10,此时每一个匿名函数都引用着保存变量i的同一个变量对象。所以每个函数内部i的值都是10。
但是我们可以通过创建另一个匿名函数强制让闭包的行为符合预期
在这里,我们并没有直接把闭包赋值给数组,而是定义了一个匿名函数。并将立即执行该匿名函数的结果赋值给数组。
这里的匿名函数有一个参数num。也就是最终的函数要返回的值,在调用每个匿名函数时,我们传入了变量i,由于函数的参数是按值传递,所以就会将变量i的当前值复制给参数num,而在这个匿名函数内部,又创建并返回一个访问num的闭包,这样以来,result数组中的每一个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值。
块级作用域(通常为私有作用域)
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。可能这种语法有点不好理解。我们可以这样理解:
无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。例如:
因为在for循环外部插入了一个私有作用域,在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。
在大型应用程序中,我们都应该尽量少向全局作用域添加变量和函数,因为过多的全局变量和函数,很容易导致命名冲突。通过创建私有作用域,每个开发人员可以使用自己的变量,又不必担心搞乱全局。
且这种做法可以减少闭包占用的内存问题。因为没有匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
私有变量
严格来说,在javascript中没有私有成员的概念,所有对象的属性都是共有的,但是有一个私有变量的概念:任何函数中定义的变量,都可以认为是私有变量,因此不能在函数的外部访问这些变量。
私有变量:包括函数的参数,局部变量和函数内部定义的其他函数。
在函数的内部,我们定义了三个变量 sum, num1, num2。 在函数内部可以访问这几个变量,但是在函数的外部则不能访问他们。如果在函数内部建一个闭包,闭包通过自己的作用域链也可以访问到这些变量。我们可以利用这种,创建用于访问私有变量的共有方法。
特权方法
我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方法。
第一种:在构造函数中定义特权方法
对这个例子而言, privateVariable和privateFunction()只能通过特权方法publicMethod来访问。在创建
MyObject的实例后,除了使用publicMethod()方法这一个途径外,没有任何方法可以直接访问privateVariable和privateFunction()
利用私有和特有成员,我们可以隐藏那些不应该被直接修改的数据。例如:
以上代码定义了两个特权方法,getName()和setName(),这两个函数在构造函数外部也可以访问,而且有权访问私有变量name.但在构造函数的外部,没有任何办法访问name。
这两个方法是在构造函数内部定义的,他们作为闭包能够通过作用域链访问name,私有变量name在Person的每一个实例都是不同的,每次调用这个构造函数,都会重新创建这两个方法。
利用构造函数定义特权方法,有一个缺点,那就是必须使用构造函数,而构造函数模式针对每一组实例,都会创建同样一组新方法。
静态私有变量
通过在私有作用域中定义私有变量和函数,同样可以创建特权方法。
这个模式创建一个私有作用域,并在其中封装了一个构造函数及相应的方法。我们把公有方法定义在原型上面,这里体现了原型模式,我们创建了函数的表达式,并没有创建函数声明,因为函数声明只能创建局部变量,,而且出于同样的原因,我们也没有在声明MyObject时,使用var关键字。
记住:初始化未经声明的变量,总会创建一个全局变量,所以MyObject是一个全局变量,但是,我们在严格模式下,给未经声明的变量赋值会报错的。
这个方法与刚才在构造函数中定义特权方法的区别在于:私有变量个函数是共享的。由于特权方法在原型上定义的,因此所有实例都使用同一个函数,而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。
模块模式
模块模式:为单例创建私有变量和特权方法。
单例:指的是只有一个实例的对象。按照惯例,javascript中以对象字面量的方式来创建单例对象。
这个模块模式使用了一个返回对象的匿名函数,在这个匿名函数,首先定义了私有变量和函数,然后将一个对象字面量作为函数返回。返回的对象字面量只包含可以公开的属性和方法,由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。
从本质上来讲:这个对象字面量定义的是单例的公共接口。这种模式在需要对单例初始化,同时又维护其私有变量时,是非常有用的。
如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以使用模块模式。以这种模式创建的每一个单例都是Object的实例。因为最终要通过一个对象字面的方法来表示它。
增强模块模式
这种增强的模块模式适合那些单例必须是某种类型的实例。同时还必须添加某些属性或方法对其加以增强的情况。
The text was updated successfully, but these errors were encountered: