摘要:概述函數的聲明命令函數表達式變量賦值命令后面不帶有函數名。如果加上函數名,該函數名只在函數體內部有效,在函數體外部無效。同樣的,函數體內部聲明的函數,作用域綁定函數體內部。可以通過,達到調用函數自身的目的。
函數
函數是一段可以反復調用的代碼塊。函數還能接受輸入的參數,不同的參數會返回不同的值。
1.概述
1.1函數的聲明
1.2函數的重復聲明
1.3圓括號運算符,return 語句和遞歸
1.4第一等公民
1.5函數名的提升
2函數的屬性和方法
2.1name 屬性
2.2length 屬性
2.3toString()
3函數作用域
3.1定義
3.2函數內部的變量提升
3.3函數本身的作用域
4參數
4.1概述
4.2參數的省略
4.3傳遞方式
4.4同名參數
4.5arguments 對象
5.函數的其他知識點
5.1閉包
5.2立即調用的函數表達式(IIFE)
6eval 命令
6.1基本用法
6.2eval 的別名調用
函數
函數是一段可以反復調用的代碼塊。函數還能接受輸入的參數,不同的參數會返回不同的值。
1.概述
1.1函數的聲明
(1)function 命令
(2)函數表達式 變量賦值
var print = function(s) {}
function命令后面不帶有函數名。如果加上函數名,該函數名只在函數體內部有效,在函數體外部無效。
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
用處有兩個,一是可以在函數體內部調用自身,二是方便除錯(除錯工具顯示函數調用棧時,將顯示函數名,而不再顯示這里是一個匿名函數)。
(3)Function 構造函數
var add = new Function(
"x",
"y",
"return x + y"
);除了最后一個參數是add函數的“函數體”,其他參數都是add函數的參數
// 等同于
function add(x, y) {
return x + y;
}
1.2函數的重復聲明
由于函數名的提升
后面的聲明就會覆蓋前面的聲明
1.3圓括號運算符,return 語句和遞歸
圓括號用于調用函數a()
遇到return后面句子都不執行,沒有就返回undefined
函數可以調用自身,這就是遞歸(recursion)。下面就是通過遞歸,計算斐波那契數列的代碼。
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
1.4第一等公民
函數只是一個可以執行的值
函數只是一個可以執行的值
function add(x, y) {
return x + y;
}
// 將函數賦值給一個變量
var operator = add;
// 將函數作為參數和返回值
function a(op){
return op;
}
a(add)(1, 1)
// 2
1.5函數名的提升
視為變量名,所以采用function命令聲明函數時,整個函數會像變量聲明一樣
表達式形式是匿名函數
f();
var f = function (){};
// TypeError: undefined is not a function
上面的代碼等同于下面的形式。
var f;
f();
f = function () {};
上面代碼第二行,調用f的時候,f只是被聲明了,還沒有被賦值,等于undefined,所以會報錯。因此,如果同時采用function命令和賦值語句聲明同一個函數,最后總是采用賦值語句的定義。 因為兩個都是聲明 ,然后賦值 function形式是聲明 var聲明 最后賦值
var f = function () {
console.log("1");
}
function f() {
console.log("2");
}
f() // 1
2函數的屬性和方法
2.1name 屬性
2.1.1function f1() {}
f1.name // "f1"
如果是通過變量賦值定義的函數,那么name屬性返回變量名。
2.2.2var f2 = function () {};
f2.name // "f2"
如果變量的值是一個具名函數,那么name屬性返回function關鍵字之后的那個函數名。
2.2.3var f3 = function myName() {};
f3.name // "myName"
f3.name返回函數表達式的名字。注意,真正的函數名還是f3,而myName這個名字只在函數體內部可用
2.2.4name屬性的一個用處,就是獲取參數函數的名字。
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) // myFunc
2.2length 屬性
函數定義之中的參數個數。
2.3toString()
返回一個字符串,內容是函數的源碼。
Math.sqrt.toString()
// "function sqrt() { [native code] }
利用這一點,可以變相實現多行字符串。
var multiline = function (fn) {
var arr = fn.toString().split("n");
return arr.slice(1, arr.length - 1).join("n");
};
function f() {/*
這是一個
多行注釋
*/}
multiline(f);
// " 這是一個
// 多行注釋"
3函數作用域
3.1定義
3.1.1全局 一直存在在內存 到處可讀取
3.1.2 函數作用域 變量只在函數種存在
對于var命令來說,局部變量只能在函數內部聲明,在其他區塊中聲明,一律都是全局變量。
3.2函數內部的變量提升
函數作用域內部也會產生“變量提升”現象。var命令聲明的變量,不管在什么位置,變量聲明都會被提升到函數體的頭部
3.3函數本身的作用域
作用域只與聲明的地方有關
函數本身也是一個值,也有自己的作用域。它的作用域與變量一樣,就是其聲明時所在的作用域,與其運行時所在的作用域無關
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
函數x是在函數f的外部聲明的,所以它的作用域綁定外層,內部變量a不會到函數f體內取值,所以輸出1,而不是2。
3.3.1同樣的,函數體內部聲明的函數,作用域綁定函數體內部。
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
上面代碼中,函數foo內部聲明了一個函數bar,bar的作用域綁定foo。當我們在foo外部取出bar執行時,變量x指向的是foo內部的x,而不是foo外部的x。正是這種機制,構成了下文要講解的“閉包”現象。
4參數
4.1概述
4.2參數的省略
4.1.1無論提供多少個參數(或者不提供參數),JavaScript 都不會報錯。省略的參數的值就變為undefined。需要注意的是,函數的length屬性與實際傳入的參數個數無關
4.1.2沒有辦法只省略靠前的參數,而保留靠后的參數。如果一定要省略靠前的參數,只有顯式傳入undefined。
function f(a, b) {
return a;
}
f( , 1) // SyntaxError: Unexpected token ,(…)
f(undefined, 1) // undefined
上面代碼中,如果省略第一個參數,就會報錯。
4.3傳遞方式
4.3.1函數參數如果是原始類型的值(數值、字符串、布爾值),傳遞方式是傳值傳遞。在函數里面的是復制的另外一個值了
在函數體內修改參數值,不會影響到函數外部
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
上面代碼中,變量p是一個原始類型的值,傳入函數f的方式是傳值傳遞。因此,在函數內部,p的值是原始值的拷貝,無論怎么修改,都不會影響到原始值。
4.3.2如果函數參數是復合類型的值(數組、對象、其他函數),傳遞方式是傳址傳遞
傳入函數的原始值的地址,因此在函數內部修改參數,將會影響到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
上面代碼中,傳入函數f的是參數對象obj的地址。因此,在函數內部修改obj的屬性p,會影響到原始值。(外面和里面指向同一個地址)
4.3.3注意,如果函數內部修改的,不是參數對象的某個屬性,而是替換掉整個參數,這時不會影響到原始值。
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
形式參數(o)的值實際是參數obj的地址,重新對o賦值導致o指向另一個地址,保存在原地址上的值當然不受影響。(外面和里面指向不同地址了)
4.4同名參數
如果有同名的參數,則取最后出現的那個值
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
取值的時候,以后面的a為準,即使后面的a沒有值或被省略,也是以其為準。
function f(a, a) {
console.log(a);
}
f(1) // undefined
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1
4.5arguments 對象
4.5.1只能在函數體內用
正常可以修改 嚴格無效
有length屬性
var f = function(a, b) {
arguments[0] = 3;
arguments[1] = 2;
return a + b;
}
f(1, 1) // 5
4.5.2與數組的關系
數組專有的方法(比如slice和forEach),不能在arguments對象上直接使用
兩種常用的轉換方法:slice方法和逐一填入新數組。
var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
4.5.3callee屬性
arguments對象帶有一個callee屬性,返回它所對應的原函數。
var f = function () {
console.log(arguments.callee === f);
}
f() // true
可以通過arguments.callee,達到調用函數自身的目的。這個屬性在嚴格模式里面是禁用的,因此不建議使用。
5.函數的其他知識點
5.1閉包
定義在函數內的函數
用處 1.讀取函數內的變量
2.讓這些變量始終保存在內存中
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
上面代碼中,start是函數createIncrementor的內部變量。通過閉包,start的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算。從中可以看到,閉包inc使得函數createIncrementor的內部環境,一直存在。所以,閉包可以看作是函數內部作用域的一個接口。
為什么會這樣呢?原因就在于inc始終在內存中,而inc的存在依賴于createIncrementor,因此也始終在內存中,不會在調用結束后,被垃圾回收機制回收。
因為inc是全局的,使得閉包可以一直存在同時訪問內部變量
3封裝私有變量
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name, getAge: getAge, setAge: setAge
};
}
var p1 = Person("張三");
p1.setAge(25);
p1.getAge() // 25
上面代碼中,函數Person的內部變量_age,通過閉包getAge和setAge,變成了返回對象p1的私有變量
注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,所以內存消耗很大。因此不能濫用閉包,否則會造成網頁的性能問題
5.2立即調用的函數表達式(IIFE)
()是運算符 表達式
function(){ / code / }();
// SyntaxError: Unexpected token (
產生這個錯誤的原因是,function這個關鍵字即可以當作語句,也可以當作表達式。
如果function關鍵字出現在行首,一律解釋成語句。因此,JavaScript 引擎看到行首是function關鍵字之后,認為這一段都是函數的定義,不應該以圓括號結尾
// 語句
function f() {}
// 表達式
var f = function f() {}
5.2.1最簡單的處理,就是將其放在一個圓括號里面。
(function(){ / code / }());
// 或者
(function(){ / code / })();
上面兩種寫法都是以圓括號開頭,引擎就會認為后面跟的是一個表示式,而不是函數定義語句,所以就避免了錯誤。這就叫做“立即調用的函數表達式
任何讓解釋器以表達式來處理函數定義的方法,都能產生同樣的效果,比如下面三種寫法。
var i = function(){ return 10; }();
true && function(){ / code / }();
0, function(){ / code / }();
甚至像下面這樣寫,也是可以的。
!function () { / code / }();
~function () { / code / }();
-function () { / code / }();
+function () { / code / }();
5.2.2目的
1.不必為函數命名,避免污染全局變量
2.封裝私有變量
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
上面代碼中,寫法二比寫法一更好,因為完全避免了污染全局變量
6eval() 命令
6.1接受一個字符串當參數并當語錄執行 否則報錯
eval("var a = 1;");
a // 1
eval("3x") // Uncaught SyntaxError: Invalid or unexpected token
6.2
放在eval中的字符串,應該有獨自存在的意義,不能用來與eval以外的命令配合使用。舉例來說,下面的代碼將會報錯。
eval("return;"); // Uncaught SyntaxError: Illegal return statement
6.3如果eval的參數不是字符串,那么會原樣返回。
eval(123) // 123
6.4作用域
var a = 1;
eval("a = 2");
a // 2
上面代碼中,eval命令修改了外部變量a的值。由于這個原因,eval有安全風險
如果使用嚴格模式,(自己聲明的不會影響,上面的問題依然有)eval內部聲明的變量,不會影響到外部作用域。
(function f() {
"use strict";
eval("var foo = 123");
console.log(foo); // ReferenceError: foo is not defined
})()
(function f() {
"use strict";
var foo = 1;
eval("foo = 2");
console.log(foo); // 2
})()
eval最常見的場合是解析 JSON 數據的字符串,不過正確的做法應該是使用原生的JSON.parse方法
6.1基本用法
6.2eval 的別名調用
1.eval不利于引擎優化執行速度
2.引擎在靜態代碼分析的階段,根本無法分辨執行
的是eval。
var m = eval;
m("var x = 1");
x // 1
上面代碼中,變量m是eval的別名。靜態代碼分析階段,引擎分辨不出m("var x = 1")執行的是eval命令
為了保證eval的別名不影響代碼優化改進:用別名的話,里面都是全局變量
var a = 1;
function f() {
var a = 2;
var e = eval;
e("console.log(a)");
}
f() // 1
上面代碼中,eval是別名調用,所以即使它是在函數中,它的作用域還是全局作用域,因此輸出的a為全局變量。這樣的話,引擎就能確認e()不會對當前的函數作用域產生影響,優化的時候就可以把這一行排除掉。
eval的別名調用的形式五花八門,只要不是直接調用,都屬于別名調用,因為引擎只能分辨eval()這一種形式是直接調用。
eval.call(null, "...")
window.eval("...")
(1, eval)("...")
(eval, eval)("...")
上面這些形式都是eval的別名調用,作用域都是全局作用域。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/105995.html
摘要:詞法作用域的查找規則是閉包的一部分。因此的確同閉包息息相關,即使本身并不會真的使用閉包。而上面的創建一個閉包,本質上這是將一個塊轉換成一個可以被關閉的作用域。結合塊級作用域與閉包模塊這個模式在中被稱為模塊。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具有許多復雜微妙技術的語言,即使是經驗豐富的 Jav...
摘要:中函數是一等公民。小明小明調用函數時,傳遞給函數的值被稱為函數的實參值傳遞,對應位置的函數參數名叫作形參。所以不推薦使用構造函數創建函數因為它需要的函數體作為字符串可能會阻止一些引擎優化也會引起瀏覽器資源回收等問題。 函數 之前幾節中圍繞著函數梳理了 this、原型鏈、作用域鏈、閉包等內容,這一節梳理一下函數本身的一些特點。 javascript 中函數是一等公民。 并且函數也是對象,...
摘要:盡可能的使用局部變量,少用全局變量。正確的實現就是在函數體內部使用將聲明成局部變量。在新特性中,引入了塊級作用域這個概念,因此還可以使用,來聲明局部變量。它們共享外部變量,并且閉包還可以更新的值。 變量作用域 作用域,對于JavaScript語言來說無處不在,變量作用域,函數作用域(運行時上下文和定義時上下文),作用域污染等等都跟作用域息息相關,掌握JavaScript作用于規則,可以...
閱讀 1269·2021-09-22 15:18
閱讀 2599·2021-09-22 15:17
閱讀 2226·2019-08-30 15:55
閱讀 1573·2019-08-30 15:54
閱讀 1042·2019-08-30 13:12
閱讀 624·2019-08-30 13:12
閱讀 1676·2019-08-29 11:33
閱讀 1438·2019-08-26 17:04