{"action":"opened","issue":{"url":"https://api.github.com/repos/z2x/blog/issues/24","repository_url":"https://api.github.com/repos/z2x/blog","labels_url":"https://api.github.com/repos/z2x/blog/issues/24/labels{/name}","comments_url":"https://api.github.com/repos/z2x/blog/issues/24/comments","events_url":"https://api.github.com/repos/z2x/blog/issues/24/events","html_url":"https://github.com/z2x/blog/issues/24","id":301627919,"number":24,"title":"JavaScript函数与作用域","user":{"login":"z2x","id":24654957,"avatar_url":"https://avatars3.githubusercontent.com/u/24654957?v=4","gravatar_id":"","url":"https://api.github.com/users/z2x","html_url":"https://github.com/z2x","followers_url":"https://api.github.com/users/z2x/followers","following_url":"https://api.github.com/users/z2x/following{/other_user}","gists_url":"https://api.github.com/users/z2x/gists{/gist_id}","starred_url":"https://api.github.com/users/z2x/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/z2x/subscriptions","organizations_url":"https://api.github.com/users/z2x/orgs","repos_url":"https://api.github.com/users/z2x/repos","events_url":"https://api.github.com/users/z2x/events{/privacy}","received_events_url":"https://api.github.com/users/z2x/received_events","type":"User","site_admin":false},"labels":[{"id":852761665,"url":"https://api.github.com/repos/z2x/blog/labels/study%20note","name":"study note","color":"7add7a","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-03-02T02:06:08Z","updated_at":"2018-03-02T02:06:08Z","closed_at":null,"author_association":"OWNER","body":"# var变量声明\r\n\r\nECMAScript的变量属于松散型，可以用来保存任何类型的数据。每个变量仅仅是一个用于保存值的占位符。\r\n\r\n定义变量时需要使用var操作符,后面跟变量名：\r\n\r\n```javascript\r\nvar name = \"blog\";\r\n```\r\n\r\n使用var定义的变量属于局部变量，离开函数后即实效；\r\n\r\n省略var操作符直接创建的变量属于全局变量，可以在函数外部的任何部分被引用。\r\n\r\n可以使用一条语句创建多个变量，只需使用（,）分隔开，如：\r\n\r\n```javascript\r\nvar name = \"blog\";\r\n    age = 2;\r\n    city = beijing;\r\n```\r\n* var重复声明一个已经存在的变量，原变量值不变\r\n* 不写var会声明一个全局的变量，这是我们在编程中应该要避免的，即使真的需要全局变量，也应该在最外层作用域使用var声明\r\n\r\n\r\n\r\n# 函数是什么\r\nJavaScript函数是指一个特定代码块，可能包含多条语句，可以通过名字来供其它语句调用以执行函数包含的代码语句。\r\n\r\n# 函数的声明\r\n\r\nECMAScript规定了三种声明函数方式：\r\n\r\n1. 构造函数\r\n\r\nECMAScript中的构造函数可以用来创建特定类型的对象。\r\n\r\n构造函数始终都应该以一个大写字母开头，而非构造函数则应该以一个小写字母开头。\r\n\r\n要创建一个新的构造函数实例，必须使用`new`操作符。以这种方式调用构造函数会经历以下4个步骤：\r\n\r\n  （1）创建一个新对象；\r\n\r\n  （2）将构造函数的作用域赋给新对象（this指向该新对象）；\r\n\r\n  （3）执行构造函数中的代码（为这个新对象添加属性）；\r\n\r\n  （4）返回新对象。\r\n\r\n例如：\r\n```javascript\r\nfunction Person(name,age,job){\r\n  this.name = name;\r\n  this.age = age;\r\n  this.job = job;\r\n  this.sayName = function(){\r\n    console.log(this.name);\r\n  };\r\n}\r\n\r\nvar person1 = new Person(\"Banana\",18,\"fruit\");\r\nvar person2 = new Person(\"Potato\",1,\"vegetable\");\r\n\r\nperson1.sayName();  //Banana\r\nperson2.sayName();  //Potato\r\n```\r\n\r\n在上面例子中，person1和person2分别保存着Person的一个不同的实例。\r\n\r\n使用constructor属性，能够返回对象的构造函数：\r\n\r\n```javascript\r\nconsole.log(person1.constructor == Person); //true\r\nconsole.log(person1.constructor == Person); //true\r\n```\r\n\r\n2. 函数声明\r\n\r\n使用function关键字可以声明一个函数。\r\n\r\n语法：\r\n```javascript\r\nfunction name([param,[, param,[..., param]]]) {\r\n   [statements]\r\n}\r\n//name -> 函数名\r\n//param ->要传递给函数的参数的名称。不同引擎中的最大参数数量不同。\r\n//statements ->包含函数体的语句。\r\n```\r\n例如：\r\n```javascript\r\n//函数声明\r\nfunction sayHello(){\r\n  console.log('hello')\r\n}\r\n\r\n//函数调用\r\nsayHello()\r\n```\r\n\r\n3. 函数表达式\r\n\r\n同样使用function关键字来声明一个函数。\r\n\r\n语法：\r\n```javascript\r\nlet function_expression = function [name]([param1[, param2[, ..., paramN]]]) {\r\n   statements\r\n};\r\n//name -> 函数名称。可被忽略，此种情况下的函数是匿名函数（anonymous）。 函数名称只是函数体中的一个本地变量。\r\n//paramN -> 被传递给函数的一个参数名称。一个函数至多拥有 255 个参数。\r\n//statements -> 构成函数体的语句。\r\n```\r\n例如：\r\n```javascript\r\nvar sayHello = function(){\r\n  console.log('hello');\r\n};\r\n\r\nsayHello();\r\n```\r\n\r\n**函数声明和函数表达式的区别**\r\n\r\n>函数表达式（function expression）非常类似于函数声明（function statement）（详情查看函数声明），并且拥有几乎相同的语法。函数表达式与函数声明的最主要区别是函数名称（function name），在函数表达式中可忽略它，从而创建匿名函数（anonymous functions）。一个函数表达式可以被用作一个IIFE（即时调用的函数表达式），它一旦定义就运行。\r\n\r\n* 函数声明的重要特征是函数声明提升，也就是说在执行代码之前会先读取函数声明，这就意味着可以把函数声明放在调用它的语句后面。\r\n\r\n```javascript\r\nsayHi();//函数调用\r\nfunction sayHi(){\r\n    console.log(\"Hello World!\");\r\n}//函数声明\r\n```\r\n\r\n```javascript\r\nfunction sayHi(){\r\n    console.log(\"Hello World!\");\r\n}\t//函数声明\r\nsayHi();//函数调用\r\n```\r\n\r\n* 函数表达式在使用前必须先赋值，因此函数声明必须放在函数调用前面。\r\n\r\n```javascript\r\nvar sayHi = function(){\r\n    console.log(\"Hello World!\");\r\n}//函数声明\r\nsayHi();//函数调用\r\n```\r\n\r\n\r\n# hoisting机制\r\n\r\n## 变量提升\r\n\r\n变量提升是将变量提升到函数级作用域的最顶端。但只是提升变量的声明，并不会把赋值也提升上来。\r\n\r\n例如：\r\n\r\n```javascript\r\nconsole.log(a);\t\t//undefined\r\nvar a = 1;\r\nconsole.log(a);\t\t//1\r\n```\r\n\r\n对应的解析流程为：\r\n\r\n```javascript\r\nvar a;\r\nconsole.log(a);\t\t//undefined\r\nvar a = 1;\r\nconsole.log(a);\t\t//1\r\n```\r\n\r\n## 函数提升\r\n\r\n函数提升是将将整个函数都提到整个作用域的最顶端。连同函数体一起提升。\r\n\r\n**只有函数声明形式才能被提升**\r\n\r\n```javascript\r\nsayHi();\t//函数调用\r\nfunction sayHi(){\r\n    console.log(\"Hello World!\");\r\n}\r\n```\r\n\r\n\r\n\r\n# 函数参数\r\n\r\n函数运行的时候，有时需要提供外部数据，不同的外部数据会得到不同的结果，这种外部数据就叫参数。\r\n\r\n**当函数参数存在同名的参数，则取最后出现的那个值。**\r\n\r\n```javascript\r\nfunction sayHello(name,age){\r\n  console.log('hello ' +  name + age);\r\n}\r\n//函数参数可以为1个，也可以为多个\r\n// name,age便是函数sayHello的参数\r\nsayHello('a',1);        //hello a1\r\nsayHello('b',1,true);   //hello b1\r\n```\r\n\r\n## arguments对象\r\n\r\n1. 可以使用arguments对象读取函数内部的所有传入参数。\r\n\r\n```javascript\r\nfunction consoleParameter(parameter){\r\n  console.log(arguments[0]);\r\n  console.log(arguments[1]);\r\n}\r\nconsoleParameter(1,2);\r\n//1\r\n//2\r\n```\r\n2. 还可以使用arguments的length属性来获取参数数量。\r\n```javascript\r\nfunction consoleParameter(parameter){\r\n  console.log(arguments.length);\r\n}\r\nconsoleParameter(1,2);\r\n//2\r\n```\r\n# 重载\r\n\r\n在JS中没有重载，同名函数会覆盖。\r\n但可以在函数体针对不同的参数调用执行相应的逻辑。\r\n```javascript\r\n function printPeopleInfo(name, age, sex){\r\n    if(name){\r\n      console.log(name);\r\n    }\r\n\r\n    if(age){\r\n      console.log(age);\r\n    }\r\n\r\n    if(sex){\r\n      console.log(sex);\r\n    }\r\n  }\r\n\r\n\r\n  printPeopleInfo('Byron', 26);\r\n  printPeopleInfo('Byron', 26, 'male');\r\n```\r\n\r\n# 函数作用域\r\n\r\n>作用域（scope）指的是变量存在的范围。Javascript只有两种作用域：\r\n* 全局作用域，变量在整个程序中一直存在，所有地方都可以读取；\r\n* 函数作用域，变量只在函数内部存在。\r\n\r\n在函数外部声明的变量就是全局变量（global variable），它可以在函数内部读取。\r\n\r\n```javascript\r\nvar a = 1;\r\n//此处a为全局变量\r\nfunction test() {\r\n  console.log(a);\r\n}\r\n\r\ntest(); //1\r\n```\r\n\r\n在函数内部定义的变量，外部无法读取，称为“局部变量”（local variable）。\r\n```javascript\r\na = 2;\r\n//此处a为全局变量\r\nfunction test() {\r\n  var a = 1;\r\n  //此处a为局部变量\r\n  console.log(a);\r\n}\r\ntest(); //1\r\nconsole.log(a);//2\r\n```\r\n\r\n# 递归\r\n\r\n1. 自己调用自己\r\n2. 设定终止条件\r\n3. 优点: 算法简单\r\n4. 缺点: 效率低\r\n\r\n```javascript\r\n// 一个简单的阶乘函数\r\nvar f = function (x) {\r\n    if (x === 1) {\r\n        return 1;\r\n    } else {\r\n        return x * f(x - 1);\r\n    }\r\n};\r\n```\r\n\r\n# 立即调用函数表达式（IIFE）\r\n\r\n根据JavaScript引擎规定，如果`function`关键字出现在行首，一律解释成语句。那么不让`function`出现在行首，引擎便将其理解成一个表达式。最简单的处理，就是将其放在一个圆括号里面。\r\n\r\n通常情况下，只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个：\r\n\r\n1. 不必为函数命名，避免了污染全局变量；\r\n2. IIFE内部形成了一个单独的作用域，可以封装一些外部无法读取的私有变量。\r\n\r\n几种写法：\r\n```javascript\r\n(function(){\r\n  var a  = 1;\r\n})()\r\nconsole.log(a); //undefined\r\n\r\n(function fn1() {});\r\n\r\n// 在数组初始化器内只能是表达式\r\n[function fn2() {}];\r\n\r\n// 逗号也只能操作表达式\r\n1, function fn3() {};\r\n```\r\n参考信息：[《JavaScript 标准参考教程（alpha）》](http://javascript.ruanyifeng.com/grammar/function.html#toc24)\r\n\r\n小练习：\r\n\r\n1. 阶乘(Factorial)函数\r\n\r\n* 递归方法\r\n\r\n```javascript\r\nfunction factorial(num) {\r\n  // 如果num是一个小于0的整数，就会拒绝\r\n  if (num < 0) {\r\n    return \"Error\";\r\n  }\r\n  else\r\n  if (num === 0 || num === 1) {\r\n    // 如果num是一个0或者1，其阶乘是1\r\n    return 1;\r\n  } else {\r\n    // 调用递归\r\n    return (num * factorial(num - 1));\r\n  }\r\n}\r\nfactorial(4);   //24\r\n```\r\n\r\n* `while`循环方法：\r\n\r\n```javascript\r\nfunction factorial(num) {\r\n  // 第一步：创建一个变量result来存储num结果\r\n  var result = num;\r\n  // 如果num是一个小于0的整数，就会拒绝\r\n  if (num < 0) {\r\n    return \"Error\";\r\n    // 如果num是一个0或者1，其阶乘是1\r\n  } else if (num === 0 || num === 1) {\r\n    return 1;\r\n  } else {\r\n    // 使用while循环求值\r\n    while (num > 1) {\r\n      num--;\r\n      result *= num;\r\n    }\r\n    return result;\r\n  }\r\n}\r\nfactorial(4);   //24\r\n```\r\n\r\n* `for`循环方法：\r\n\r\n```javascript\r\nfunction factorial(num) {\r\n  // 如果num是一个小于0的整数，就会拒绝\r\n  if (num < 0) {\r\n    return \"Error\";\r\n    // 如果num是一个0或者1，其阶乘是1\r\n  } else if (num === 0 || num === 1) {\r\n    return 1;\r\n  } else {\r\n    // 使用for循环求值\r\n    for(var i = num - 1; i >= 1; i--){\r\n      num *= i;\r\n    }\r\n    return num;\r\n  }\r\n}\r\nfactorial(4);   //24\r\n```\r\n\r\n2. 写出返回值\r\n\r\n```javascript\r\nfunction getInfo(name, age, sex){\r\n  console.log('name:',name);\r\n  console.log('age:', age);\r\n  console.log('sex:', sex);\r\n  console.log(arguments);\r\n  arguments[0] = 'valley';\r\n  console.log('name', name);\r\n}\r\n\r\ngetInfo('小谷', 2, '男');\r\n// name: 饥人谷\r\n// age: 2\r\n// sex: 男\r\n// { '0': '小谷', '1': 2, '2': '男' }\r\n// name valley\r\ngetInfo('小谷', 3);\r\n// name: 小谷\r\n// age: 3\r\n// sex: undefined\r\n// { '0': '小谷', '1': 3 }\r\n// name valley\r\ngetInfo('男');\r\n// name: 男\r\n// age: undefined\r\n// sex: undefined\r\n// { '0': '男' }\r\n// name valley\r\n```\r\n\r\n3. 写一个函数，返回参数的平方和\r\n\r\n```javascript\r\nfunction sumOfSquares() {\r\n  var sum = 0;\r\n  for (i = 0; i < arguments.length; i++) {\r\n    sum += arguments[i] * arguments[i];\r\n  }\r\n  return sum;\r\n}\r\nvar result = sumOfSquares(2, 3, 4);\r\nvar result2 = sumOfSquares(1, 3);\r\nconsole.log(result); //29\r\nconsole.log(result2); //10\r\n```\r\n\r\n4. 如下代码的输出？为什么\r\n\r\n```javascript\r\nconsole.log(a);\t\t//undefined\r\nvar a = 1;\r\nconsole.log(b);\t\t//b is not defined\r\n```\r\n\r\n变量声明提升，赋值没有提升，`console.log(a);` ——> `var a;console.log(a);`；\r\n\r\n`b`没有声明也没有赋值。\r\n\r\n5. 如下代码的输出？为什么\r\n\r\n```javascript\r\nsayName('world');\r\nsayAge(10);\r\n\r\nfunction sayName(name) {\r\n  console.log('hello ', name);    //hello  world\r\n}\r\nvar sayAge = function (age) {\r\n  console.log(age);\r\n};                                //sayAge is not a function\r\n```\r\n\r\n`function sayName(name)`是一个函数声明，调用语句可以放在函数声明前面也可以放在后面；\r\n\r\n`var sayAge = function(age)`则是一个函数表达式，调用语句只能放在函数声明后面。\r\n\r\n\r\n\r\n"}}
{"actor":{"display_login":"z2x"}}