阿里雲最近在作活動,低至2折,有興趣能夠看看:html
promotion.aliyun.com/ntms/yunpar…git
函數是完成某個特定功能的一組語句。如沒有函數,完成任務可能須要五行、十行、甚至更多的代碼。這時咱們就能夠把完成特定功能的代碼塊放到一個函數裏,直接調用這個函數,就省重複輸入大量代碼的麻煩。github
函數能夠歸納爲:一次封裝,四處使用。數組
函數的定義方式一般有三種:函數聲明方式、函數表達式、 使用Function構造函數 。瀏覽器
函數聲明方式bash
語法:微信
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中運行任何的代碼都是在執行上下文中運行。
執行上下文的類型,主要有兩類:
全局執行上下文:這是默認的,最基礎的執行上下文。不在任何函數中的代碼都位於全局執行上下文中。共有兩個過程:1.建立有全局對象,在瀏覽器中這個全局對象就是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折,有興趣能夠看看:promotion.aliyun.com/ntms/yunpar…
乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。
由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。
每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵