函式宣告方式 你真的懂函式嗎
函式宣告方式
匿名函式
function後面直接跟括號,中間沒有函式名的就是匿名函式。
let fn = function() {
console.log('我是fn')
}
let fn2 = fn
console.log(fn.name) //fn
console.log(fn2.name)//fn,fn和fn2指向的是同一個function。
具名函式
function後面有函式名字的,不是直接跟括號的的就是具名函式。
如果把一個具名函式賦值給一個變數,那麼這個具名函式的作用域就不是window了。
let fn = function fn1() {
console.log('function')
}
console .log(fn.name) //fn1
console.log(fn1,name) // ReferenceError: fn1 is not defined
箭頭函式
箭頭函式是es6知識點,具有以下幾個特點:
- 如果只有一個引數,可以省略小括號。
- 如果有至少有兩個引數,必須加小括號。
- 如果函式體只有一句話可以省略花括號,並且這一句作為返回值return。
- 如果函式體至少有兩句必須加上花括號。
- 箭頭函式裡面是沒有this的。
let fn = e => e+1
console.log(fn(1)) //2
let fn1 = (i,y) => i+y
console.log(fn1(2,3)) //5
let fn2 = (i,y) => {
i+=1;
y+=2;
return i+y
}
console.log(fn2(5,6)) //13
詞法作用域(靜態作用域)
靜態作用域又叫做詞法作用域,採用詞法作用域的變數叫詞法變數。詞法變數有一個在編譯時靜態確定的作用域。詞法變數的作用域可以是一個函式或一段程式碼,該變數在這段程式碼區域內可見(visibility);在這段區域以外該變數不可見(或無法訪問)。詞法作用域裡,取變數的值時,會檢查函式定義時的文字環境,捕捉函式定義時對該變數的繫結。
詞法作用域:變數的作用域是在定義時決定而不是執行時決定,也就是說詞法作用域取決於原始碼,通過靜態分析就能確定,因此詞法作用域也叫做靜態作用域。 with和eval除外,所以只能說JS的作用域機制非常接近詞法作用域(Lexical scope)。
通過詞法作用域樹能判斷變數指向關係,但是不能斷定變數的值,變數的值還需要根據執行順序進一步作出判斷,看一下例子:
因為JavaScript採用的是詞法作用域,bian'liang的作用域基於函式建立的位置,跟呼叫時的位置無關。
var i = 1,
j = 2,
k = 3;
function a(o, p, x, q) {
var x = 4;
alert(i);
function b(r, s) {
var i = 11,
y = 5;
alert(i);
function c(t) {
var z = 6;
alert(i);
};
var d = function() {
alert(y);
};
c(60);
d();
};
b(40, 50);
}
a(10, 20, 30); //1 11 11 5
/**
* 模擬建立一棵語法分析樹,儲存function內的變數和方法
*/
var SyntaxTree = {
// 全域性物件在語法分析樹中的表示
window: {
variables:{
i:{ value:1},
j:{ value:2},
k:{ value:3}
},
functions:{
a: this.a
}
},
a:{
variables:{
x:'undefined'
},
functions:{
b: this.b
},
scope: this.window
},
b:{
variables:{
i:'undefined'
y:'undefined'
},
functions:{
c: this.c,
d: this.d
},
scope: this.a
},
c:{
variables:{
z:'undefined'
},
functions:{},
scope: this.b
},
d:{
variables:{},
functions:{},
scope: {
scope: this.b
}
}
};
/**
* 活動物件:函式執行時建立的活動物件列表
*/
let ActiveObject = {
window: {
variables: {
i: { value: 1 }
j: { value: 2 }
k: { value: 3 }
},
functions: {
a: this.a
}
}
a: {
variables: {
x: { vale: 4 },
functions: {
b: this.b
},
scope: this.window,
params: {
o: { value: 10 },
p: { value: 20 },
x: this.variables.x
q: { vale: 'undefined' }
},
arguments: [this.params.o, this.params.p, this.params.x]
}
}
b: {
variables: {
i: { vale: 11 },
y: { vale: 5 },
},
functions: {
c: this.c,
d: this.d
},
params: {
r: { value: 40 }
s: { value: 50 }
},
arguments: [this.params.r, this.params.scope]
scope: this.a
}
c: {
variables: {
z: { value: 6 },
functions: {},
params: {
t: { value: 60 }
},
arguments: [this.params.t]
scope: this.b
}
}
d: {
variables: {},
functions: {},
params: {},
arguments: []
this.scope: this.b
}
}
call stack
進入call stack 時的一些規則:
函式的所有形參(如果我們是在函式執行上下文中)
- 由名稱和對應值組成的一個變數物件的屬性被建立;沒有傳遞對應引數的話,那麼由名稱和 undefined 值組成的一種變數物件的屬性也將被建立。
所有函式宣告(FunctionDeclaration, FD)
- 由名稱和對應值(函式物件(function-object))組成一個變數物件的屬性被建立;如果變數物件已經存在相同名稱的屬性,則完全替換這個屬性。
所有變數宣告(var, VariableDeclaration)
- 由名稱和對應值(undefined)組成一個變數物件的屬性被建立;如果變數名稱跟已經宣告的形式引數或函式相同,則變數宣告不會干擾已經存在的這類屬性。
/*
* example1:形參
*/
function test(a, b) {
/*
var a = 10
var b = undefined
根據規則1,在進入執行上下文時會自動對形參宣告並且賦值。
*/
console.log(a)
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
test(10); // 10
/*
* example2:函式宣告
*/
function test(a, b) {
console.log(a)
function a() {}
var e = function _e() {};
}
test(10); // ƒ a() {} .根據規則2,進入執行上下文會自動宣告形參並且賦值,但是同名的函式宣告會替換這個變數。
function test(a, b) {
console.log(a)
var a = 30;
var a = function _e() {};
}
test(10); // 10 .根據規則2,進入執行上下文會自動宣告形參並且賦值,但是同名的函式宣告會替換這個變數。
/*
* example3:變數宣告
*/
console.log(foo);//會打印出foo函式,根據規則3,同名的變數宣告不會干擾函式宣告和形參
function foo(){
console.log("foo");
}
var foo = 1;
this和arguments
函式呼叫
在es5中,函式有四種呼叫方式:
1. fn(p1,p2)
2. obj.fn(p1,p2)
3. fn.call(context,p1,p2)
4. fn.apply(context,p1,p2)
第三和第四種才是正常的js函式呼叫方式,其他兩種就是語法糖。
fn(p1,p2) 等價於 fn.call(undefined,p1,p2) 等價於 fn.apply(context,[p1,p2])
obj.fn(p1,p2) 等價於 obj.fn.call(obj,p1,p2) 等價於 obj.fn.apply(obj,[p1,p2])
如果你傳的 context 就 null 或者 undefined,那麼 window 物件就是預設的 context(嚴格模式下預設 context 是 undefined)
this是什麼??
this是call的第一個引數!!!!
var obj = {
foo: function(){
console.log(this)
}
}
var bar = obj.foo
obj.foo() // 打印出的 this 是 obj
bar() // 打印出的 this 是 window
obj.foo() 相當於 obj.foo.call(obj) 也就相當於把函式名前面的作為call的第一個引數,也就是this,如果沒有就是window。
bar() 相當於 bar.call(undefined)
在執行函式的時候,this是隱藏的一個引數,且必須是一個物件,如果不是,js是自動把它轉為物件。
function fn() {
console.log(this)
console.log(arguments)
}
fn.call(1,2,3) // Number {1} [2,3]
arguments
arguments是偽陣列它類似於Array,但除了length屬性和索引元素之外沒有任何Array屬性。
call和apply裡面除了第一個引數之外的都是arguments,如果arguments的個數少建議使用call,使用apply也可以,如果不確定就使用apply。
使用一下方法吧arguments轉為真正的陣列:
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
// ES2015
const args = Array.from(arguments);
const args = [...arguments]
bind
MDN 官方文件對 bind() 的定義:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
大概意思就是,bind會返回一個新的函式(並沒有的呼叫原來的函式),這個新函式會call原來的函式,call的引數由你決定。看例子:
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
var retrieveX = module.getX;
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
retrieveX.bind(module)返回了一個新的函式boundGetX,然後呼叫這個新的函式的時候,把這個函式裡面的this繫結到了module物件上,所以this.x就相當於module.x也就是等於81.
柯里化
在電腦科學中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數而且返回結果的新函式的技術。這個技術由克里斯托弗·斯特雷奇以邏輯學家哈斯凱爾·加里命名的,儘管它是Moses Schönfinkel和戈特洛布·弗雷格發明的。
說的明白一點就是,給函式傳遞一部分引數,讓它返回一個函式去處理其他引數,舉個例子,求三個數之和:
let addOne = function add(x) {
return function(y) {
return function(z) {
return x+y+z
}
}
}
let one = addOne(3)
console.log(one)//ƒ (y) {return function (z) {return x + y + z}}
let two = one(4)
console.log(two)//ƒ (z) {return x + y + z}
let three = two(5)
console.log(three)//12
高階函式
在數學和電腦科學中,高階函式是至少滿足下列一個條件的函式:
- 接受一個或多個函式作為輸入
- 輸出一個函式
舉一些高階函式的例子:
/*
*接受一個或多個函式作為輸入
*/
1. Array.prototype.filter()
2. Array.prototype.forEach()
3. Array.prototype.reduce()
4. Array.prototype.map()
5. Array.prototype.find()
6. Array.prototype.every()
/*
*輸出一個函式
*/
1. fn.bind(args)
回撥函式
函式A作為引數(函式引用)傳遞到另一個函式B中,並且這個函式B執行函式A。我們就說函式A叫做回撥函式。如果沒有名稱(函式表示式),就叫做匿名回撥函式。
名詞形式:被當做引數的函式就是回撥
動詞形式:呼叫這個回撥
注意回撥跟非同步沒有任何關係
回撥函式的使用場合
- 資源載入:動態載入js檔案後執行回撥,載入iframe後執行回撥,ajax操作回撥,圖片載入完成執行回撥,AJAX等等。
- DOM事件及Node.js事件基於回撥機制(Node.js回撥可能會出現多層回撥巢狀的問題)。
- setTimeout的延遲時間為0,這個hack經常被用到,settimeout呼叫的函式其實就是一個callback的體現
- 鏈式呼叫:鏈式呼叫的時候,在賦值器(setter)方法中(或者本身沒有返回值的方法中)很容易實現鏈式呼叫,而取值器(getter)相對來說不好實現鏈式呼叫,因為你需要取值器返回你需要的資料而不是this指標,如果要實現鏈式方法,可以用回撥函式來實現。
- setTimeout、setInterval的函式呼叫得到其返回值。由於兩個函式都是非同步的,即:他們的呼叫時序和程式的主流程是相對獨立的,所以沒有辦法在主體裡面等待它們的返回值,它們被開啟的時候程式也不會停下來等待,否則也就失去了setTimeout及setInterval的意義了,所以用return已經沒有意義,只能使用callback。callback的意義在於將timer執行的結果通知給代理函式進行及時處理。
回撥函式的傳遞
傳遞的方式有兩種,函式引用和函式表示式。
$.get('myhtmlpage.html', myCallBack);//這是對的
$.get('myhtmlpage.html', myCallBack('foo', 'bar'));//這是錯的,那麼要帶引數呢?
$.get('myhtmlpage.html', function(){//帶引數的使用函式表示式
myCallBack('foo', 'bar');
});
箭頭函式與es5的函式主要區別
箭頭函式的主要區別在this,箭頭函式是沒有this這個概念的,看例子:
setTimeout(function(a){
console.log(this) //這個this指的是{name:'Jack'}
setTimeout(function(a){
console.log(this) //這個this指的是window,因為沒有bind,呼叫setTimeout的是window
},1000)
}.bind({name:'Jack'}),1000)
setTimeout(function(a){
console.log(this) //這個this指的是{name:'Jack'}
setTimeout(function(a){
console.log(this) //這個this指的是{name: "Jack"},因為bind了外面的this也就是{name: "Jack"}
},1000)
}.bind({name:'Jack'}),1000)
setTimeout(function(a){
console.log(this) //這個this指的是{name:'Jack'}
setTimeout(a=>console.log(this),1000)//這個this指的是{name:'Jack'},因為箭頭函式沒有this的概念,它指的this就是外面的this,也就是{name:'Jack'}
}.bind({name:'Jack'}),1000)
至此基本上說了js的所有函式內容,只是簡單舉個例子
文章來源:學什麼好網