阿里云最近在作活动,低至2折,有兴趣能够看看:
https://promotion.aliyun.com/...html
函数是完成某个特定功能的一组语句。如没有函数,完成任务可能须要五行、十行、甚至更多的代码。这时咱们就能够把完成特定功能的代码块放到一个函数里,直接调用这个函数,就省重复输入大量代码的麻烦。git
函数能够归纳为:一次封装,四处使用。github
函数的定义方式一般有三种:函数声明方式、函数表达式、 使用Function构造函数 。segmentfault
函数声明方式数组
语法:浏览器
function 函数名(参数1,参数2,...){ //要执行的语句 }
例:微信
// 声明 function sum(num1, num2) { return num1 + num2; } // 调用 sum(1, 2) // 3
函数表达式闭包
语法:app
var fn = function(参数1,参数2,...){ //要执行的语句 };
例:函数
// 声明 var sum = function(num1,num2){ return num1+num2; }; // 调用 sum(1, 2) // 3
使用Function构造函数
Function构造函数能够接收任意数量的参数,最后一个参数为函数体,其余的参数则枚举出新函数的参数。其语法为:
new Function("参数1","参数2",...,"参数n","函数体");
例:
// 声明 var sum = new Function("num1","num2","return num1+num2"); // 调用 sum(1, 2) // 3
三种定义方式的区别
三种方式的区别,能够从做用域、效率以及加载顺序来区分。
从做用域上来讲,函数声明式和函数表达式使用的是局部变量,而 Function()
构造函数倒是全局变量,以下所示:
var name = '我是全局变量 name'; // 声明式 function a () { var name = '我是函数a中的name'; return name; } console.log(a()); // 打印: "我是函数a中的name" // 表达式 var b = function() { var name = '我是函数b中的name'; return name; // 打印: "我是函数b中的name" } console.log(b()) // Function构造函数 function c() { var name = '我是函数c中的name'; return new Function('return name') } console.log(c()()) // 打印:"我是全局变量 name",由于Function()返回的是全局变量 name,而不是函数体内的局部变量。
从执行效率上来讲,Function()
构造函数的效率要低于其它两种方式,尤为是在循环体中,由于构造函数每执行一次都要从新编译,而且生成新的函数对象。
来个例子:
var start = new Date().getTime() for(var i = 0; i < 10000000; i++) { var fn = new Function('a', 'b', 'return a + b') fn(i, i+1) } var end = new Date().getTime(); console.log(`使用Function构造函数方式所须要的时间为:${(end - start)/1000}s`) // 使用Function构造函数方式所须要的时间为:8.646s start = new Date().getTime(); var fn = function(a, b) { return a + b; } for(var i = 0; i < 10000000; i++) { fn(i, i+1) } end = new Date().getTime(); console.log(`使用表达式的时间为:${(end - start)/1000}s`) // 使用表达式的时间为:0.012s
因而可知,在循环体中,使用表达式的执行效率比使用 Function()
构造函数快了不少不少。因此在 Web 开发中,为了加快网页加载速度,提升用户体验,咱们不建议选择 Function ()
构造函数方式来定义函数。
最后是加载顺序,function
方式(即函数声明式)是在 JavaScript 编译的时候就加载到做用域中,而其余两种方式则是在代码执行的时候加载,若是在定义以前调用它,则会返回 undefined
:
console.log(typeof f) // function console.log(typeof c) // undefined console.log(typeof d) // undefined function f () { return 'JS 深刻浅出' } var c = function () { return 'JS 深刻浅出' } console.log(typeof c) // function var d = new Function('return "JS 深刻浅出"') console.log(typeof d) // function
函数的参数-arguments
JavaScript 中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值作任何类型检查。实际上,JavaScript 函数调用甚至不检查传入形参的个数。
function sum(a) { return a + 1; } console.log(sum(1)); // 2 console.log(sum('1')); // 11 console.log(add()); // NaN console.log(add(1, 2)); // 2
当实参比形参个数要多时,剩下的实参没有办法直接得到,须要使用即将提到的arguments
对象。
JavaScript中的参数在内部用一个数组表示。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。在函数体内能够经过arguments
对象来访问这个参数数组,从而获取传递给函数的每个参数。arguments
对象并非Array的实例,它是一个类数组对象,可使用方括号语法访问它的每个元素。
function sum (x) { console.log(arguments[0], arguments[1], arguments[2]); // 1 2 3 } sum(1, 2, 3)
arguments
对象的length
属性显示实参的个数,函数的length
属性显示形参的个数。
function sum(x, y) { console.log(arguments.length); // 3 return x + 1; } sum(1, 2, 3) console.log(sum.length) // 2
函数的参数-arguments
JavaScript 中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值作任何类型检查。实际上,JavaScript 函数调用甚至不检查传入形参的个数。
function sum(a) { return a + 1; } console.log(sum(1)); // 2 console.log(sum('1')); // 11 console.log(add()); // NaN console.log(add(1, 2)); // 2
函数的参数-同名参数
在非严格模式下,函数中能够出现同名形参,且只能访问最后出现的该名称的形参。
function sum(x, x, x) { return x; } console.log(sum(1, 2, 3)) // 3
而在严格模式下,出现同名形参会抛出语法错误。
function sum(x, x, x) { 'use strict'; return x; } console.log(sum(1, 2, 3)) // SyntaxError: Duplicate parameter name not allowed in this context
函数的参数-参数个数
当实参比函数声明指定的形参个数要少,剩下的形参都将设置为undefined
值。
function sum(x, y) { console.log(x, y); } sum(1); // 1 undefined
全部函数都有返回值,没有return
语句时,默认返回内容为undefined
。
function sum1 (x, y) { var total = x + y } console.log(sum1()) // undefined function sum2 (x, y) { return x + y } console.log(sum2(1, 2)) // 3
若是函数调用时在前面加上了new
前缀,且返回值不是一个对象,则返回this
(该新对象)。
function Book () { this.bookName = 'JS 深刻浅出' } var book = new Book(); console.log(book); // Book { bookName: 'JS 深刻浅出' } console.log(book.constructor); // [Function: Book]
若是返回值是一个对象,则返回该对象。
function Book () { return {bookName: JS 深刻浅出} } var book = new Book(); console.log(book); // { bookName: 'JS 深刻浅出' } console.log(book.constructor); // [Function: Book]
JS 一共有4种调用模式:函数调用、方法调用、构造器调用和间接调用。
当一个函数并不是一个对象的属性时,那么它就是被当作一个函数来调用的。对于普通的函数调用来讲,函数的返回值就是调用表达式的值
function sum (x, y) { return x + y; } var total = sum(1, 2); console.log(total); // 3
使用函数调用模式调用函数时,非严格模式下,this
被绑定到全局对象;在严格模式下,this
是undefined
// 非严格模式 function whatIsThis1() { console.log(this); } whatIsThis1(); // window // 严格模式 function whatIsThis2() { 'use strict'; console.log(this); } whatIsThis2(); // undefined
当一个函数被保存为对象的一个属性时,称为方法,当一个方法被调用时,this
被绑定到该对象。
function printValue(){ console.log(this.value); } var value=1; var myObject = {value:2}; myObject.m = printValue; //做为函数调用 printValue(); //做为方法调用 myObject.m();
我们注意到,当调用printValue
时,this
绑定的是全局对象(window),打印全局变量value
值1
。可是当调用myObject.m()
时,this
绑定的是方法m
所属的对象Object,因此打印的值为Object.value
,即2
。
若是函数或者方法调用以前带有关键字new
,它就构成构造函数调用。
function fn(){ this.a = 1; }; var obj = new fn(); console.log(obj.a);//1
参数处理:通常状况构造器参数处理和函数调用模式一致。但若是构造函数没用形参,JavaScript构造函数调用语法是容许省略实参列表和圆括号的。
如:下面两行代码是等价的。
var o = new Object(); var o = new Object;
函数的调用上下文为新建立的对象。
function Book(bookName){ this.bookName = bookName; } var bookName = 'JS 深刻浅出'; var book = new Book('ES6 深刻浅出'); console.log(bookName);// JS 深刻浅出 console.log(book.bookName);// ES6 深刻浅出 Book('新版JS 深刻浅出'); console.log(bookName); // 新版JS 深刻浅出 console.log(book.bookName);// ES6 深刻浅出
1.第一次调用Book()
函数是做为构造函数调用的,此时调用上下文this
被绑定到新建立的对象,即 book
。因此全局变量bookName
值不变,而book
新增一个属性bookName
,值为'ES6 深刻浅出'
;
2.第二次调用Book()
函数是做为普通函数调用的,此时调用上下为this
被绑定到全局对象,在浏览器中为window
。因此全局对象的bookNam
值改变为' 新版JS 深刻浅出'
,而book
的属性值不变。
JS 中函数也是对象,函数对象也能够包含方法,call()
和apply()
方法能够用来间接地调用函数。
这两个方法都容许显式指定调用所需的this
值,也就是说,任何函数能够做为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法均可以指定调用的实参。call()
方法使用它自有的实参列表做为函数的实参,apply()
方法则要求以数组的形式传入参数。
var obj = {}; function sum(x,y){ return x+y; } console.log(sum.call(obj,1,2));//3 console.log(sum.apply(obj,[1,2]));//3
一般来讲,一段程序代码中所用到的名字并不老是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的做用域。
词法做用域,也叫静态做用域,它的做用域是指在词法分析阶段就肯定了,不会改变。而与词法做用域相对的是动态做用域,函数的做用域是在函数调用的时候才决定的。
来个例子,以下代码所示:
var blobal1 = 1; function fn1 (param1) { var local1 = 'local1'; var local2 = 'local2'; function fn2(param2) { var local2 = 'inner local2'; console.log(local1) console.log(local2) } function fn3() { var local2 = 'fn3 local2'; fn2(local2) } fn3() } fn1()
当浏览器看到这样的代码,不会立刻去执行,它会先生成一个抽象语法树。上述代码生成的抽象语法树大概是这样的:
执行fn1
函数,fn1
中调用 fn3()
,从fn3
函数内部查找是否有局部变量 local1
,若是没有,就根据抽象树,查找上面一层的代码,也就是 local1
等于 'local1'
,因此结果会打印 'local1'
。
一样的方法查找是否有局部变量 local2
,发现当前做用域内有local2
变量,因此结果会打印 'inner local2
。
有以下的代码:
var a = 1; function fn() { console.log(a) }
两个问题:
fn
里面的变量 a
, 是否是外面的变量 a
。fn
里面的变量 a
的值, 是否是外面的变量 a
的值。对于第一个问题:
分析一个语法,就能肯定函数 fn
里面的 a
就是外面的 a
。
对于第二个问题:
函数 fn
里面的变量 a
的值, 不必定是外面的变量 a
的值,假设我们这样作:
var a = 1; function fn() { console.log(a) } a = 2 fn()
这时候当我们执行 fn()
的时候,打印 a
的值为 2
。因此若是没有看到最后,一开始我们是不知道打印的 a
值究竟是什么。
因此词法做用域只能肯定变量所在位置,并不能肯定变量的值。
执行上下文就是当前JavaScript代码被解析和执行是所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行。
执行上下文的类型,主要有两类:
window
对象。2.将this
指针指向这个全局对象。一个程序中只能存在一个执行上下文。调用栈,具备LIFO
(Last in, First out 后进先出)结构,用于存储在代码执行期间建立的全部执行上下文。
当JavaScript引擎首次读取脚本时,会建立一个全局执行上下文并将其push
到当前执行栈中。每当发生函数调用时,引擎都会为该函数建立一个新的执行上下文并push
到当前执行栈的栈顶。
引擎会运行执行上下文在执行栈栈顶的函数,根据LIFO
规则,当此函数运行完成后,其对应的执行上下文将会从执行栈中pop
出,上下文控制权将转到当前执行栈的下一个执行上下文。
看看下面的代码:
var myOtherVar = 10; function a() { console.log('myVar', myVar); b(); } function b() { console.log('myOtherVar', myOtherVar); c(); } function c() { console.log('Hello world!'); } a(); var myVar = 5;
有几个点须要注意:
a
调用下面定义的函数b
, 函数b
调用函数c
当它被执行时你指望发生什么? 是否发生错误,由于b
在a
以后声明或者一切正常? console.log
打印的变量又是怎么样?
如下是打印结果:
"myVar" undefined "myOtherVar" 10 "Hello world!"
1. 变量和函数声明(建立阶段)
第一步是在内存中为全部变量和函数分配空间。 但请注意,除了undefined
以外,还没有为变量分配值。 所以,myVar
在被打印时的值是undefined
,由于JS引擎从顶部开始逐行执行代码。
函数与变量不同,函数能够一次声明和初始化,这意味着它们能够在任何地方被调用。
因此以上代码在建立阶段时,看起来像这样子:
var myOtherVar = undefined var myVar = undefined function a() {...} function b() {...} function c() {...}
这些都存在于JS建立的全局上下文中,由于它位于全局做用域中。
在全局上下文中,JS还添加了:
window
对象,NodeJs 中是 global
对象)this
指向全局对象2. 执行
接下来,JS 引擎会逐行执行代码。
myOtherVar = 10
在全局上下文中,myOtherVar
被赋值为10
已经建立了全部函数,下一步是执行函数 a()
每次调用函数时,都会为该函数建立一个新的上下文(重复步骤1),并将其放入调用堆栈。
function a() { console.log('myVar', myVar) b() }
以下步骤:
a
函数里面没有声明变量和函数this
并指向全局对象(window)myVar
,myVar
属于全局做用域的。函数 b
,函数b
的过程跟a
同样,这里不作分析。下面调用堆栈的执行示意图:
代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具Fundebug。
阿里云最近在作活动,低至2折,有兴趣能够看看:https://promotion.aliyun.com/...
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。
每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励