函數的拓展

7.1.1 基本用法

在ES6之前,不能直接為函數的參數指定默認值,只能采用變通的方法。

function log(x.y){
y = y || 'World'l
console.log(x,y);
}log('hello')  //hello World
log('hello','Chine')  //hello Chine
log('hello','')  //hello World

上面的代碼檢查函數log的參數y有沒有賦值,如果沒有,則指定默認值為World。這種寫法的缺點在于,如果參數y賦值了,但是對應的布爾值為false,則該賦值不起作用。就像以上代碼的最后一行,參數y就等于空字符,結果被改為默認值。
為了避免這個問題嗎,通常需要先判斷一下參數y是否被賦值,如果沒有,再令其等于默認值。

if(typeof y === 'undefined'){
y = 'world';
}

ES6允許為函數的參數設置默認值,即直接寫在參數定義的后面。

function Point(x = 0,y = 0){
this.x = x;
this.y = y;
}
var p = new Point();
p // {x:0, y:0}

除了簡潔,ES6的寫法還有兩個好處:首先,閱讀代碼的人可以立刻意識到哪些參數是可以省略的,不用查看函數體或文檔;其次,有利于將來的代碼優化,及時未來的版本徹底拿掉這個參數,也不會導致以前的代碼無法運行。

參數變量是默認聲明的,所以不用let或const再次聲明。
function foo(x = 5){
let x = 1; //error
const x = 2; //error

上面的代碼中,參數變量x是默認聲明的,在函數體中不能用let或const再次聲明,否則會報錯。
使用參數默認值時,函數不能有同名參數。

function foo(x,x,y = 1){
//...
}
// SyntaxError:

另外一個容易忽略的地方是,參數默認值不是傳值的,而是每次都重新計算默認值表達式的值,也就是說,參數默認值是惰性求值的。

function foo(p = x + 1){
console.log(p);
}
foo()//100
x = 100;
foo() //101
上面代碼中,參數p默認值是x+1。這時,每次調用函數foo都會重新計算,而不是默認p等于100

7.1.2 與解構賦值默認值結合使用

參數默認值可以與解構賦值的默認值結合起來使用。

function foo({x,y = 5)}){
console.log(x,y);
}
foo({}) //undefined,5
foo({x:1}) //1,5
foo({x:1,y:2}) //1,2
foo() //TypeError:

上面的代碼使用了對象的解構賦值默認值,而沒有使用函數參數的默認值,只有當函數foo的參數是一個對象時,變量x和y才會通過解構賦值而生成。如果函數foo調用時參數不是對象,變量x和y就不會生成,從而報錯。只有參數對象沒有y屬性時,y的默認值5才會生效。
下面是另一個對象的解構賦值默認值的例子。

function fetch(url,{ bodu = '',method = 'GET',headers = {}}){
console.log(method):
}
fetch('http://example.com',{})
//“GET”
fetch('http://example.com')
//報錯

上面的代碼中,如果函數fetch的第二個參數是一個對象,就可以為他的3個屬性設置默認值。
上面的寫法不能省略第二個參數,如果結合函數參數的默認值,就可以省略第二個參數,這時就出現了雙重默認值。

function fetch(url,{method = 'GET'}={}){
console.log(method);
}
fetch('http://example.com',{})
//“GET”

上面的代碼中,函數fetch沒有第二個參數時,函數參數的默認值就會生效,然后才是結構復制的默認值生效,變量method取到默認值GET。
那么下面兩種寫法有什么差別呢?

//寫法一
function m1({x = 0,y = 0} = {}){
return {x,y};
}
//寫法二
function m2({x ,y } = {x:0,y:0}){
return {x,y};
}

上面兩種寫法都對函數的參數設定了默認值,區別在于,寫法一中函數參數的默認值是空對象,但是設置了對象解構賦值的默認值;寫法二中函數參數的默認值是一個具體屬性的函數,但是沒有設置對象結構賦值的默認值。

//函數沒有參數的情況
m1() //[0,0]
m2() //[0,0]//x和y都有值的情況
m1({x:3,y:8}) //[3,8]
m2({x:3,y:8}) //[3,8]//x有值,y無值的情況
m1({x:3}) //[3,0]
m2({}) //[0,0]
m2({x:3}) //[3,undefined]//x和y都無值的情況
m1({}) //[0,0]
m2({}) //[undefined,undefined]
m1({z:3}) //[0,0]
m2({z:3}) //[undefined,undefined]

7.1.3 參數默認值的位置

通常情況下,定義了默認值的參數應該是函數的尾參數。因為這樣比較容易看出到底省略了哪些參數。如果非尾部的參數設置默認值,實際上這個參數是無法省略的。

//列一
function f(x = 1,y){return [x, y];}f() //[1,undifned]f(2) //[2,undifned]f(,1) //報錯f(undifned,1) //[1,1]//列一
function f(x ,y = 5,z){return [x, y,z];}f() //[undifned,5,undifned]f(1) //[1,5,undifned]f(1,2) //報錯f(1,undifned,2) //[1,5,2]上面的代碼中,有默認值的參數不是尾參數。這時,無法只省略該參數而不省略其后參數,除非顯示輸入undefined。如果傳入undefined,將觸發該參數等于默認值,null則沒有這個效果。function foo(x = 5,y = 6){console.log(x,y);}foo (undefined,null)//5 null上面的代碼中,x參數對應undefined,結果觸發了默認值,y參數等于null,沒有觸發默認值。

7.1.4 函數的length屬性

指定了默認值以后,函數的length屬性返回沒有指定默認值的參數個數。也就是說,指定了默認值后,length屬性將失真。

(function (a){}).length //1
(function (a = 5){}).length //0
(function (a ,b,c = 5){}).length //2

上面的代碼中,length屬性的返回值等于函數的參數個數減去指定了默認值的參數個數。比如上面的最后一個函數定義了3個參數,其中一個參數c指定了默認值,因此length屬性等于3減去1.即2。
這時因為lenght屬性的含義是該函數預期傳入的參數個數。某個參數指定默認值以后,預期傳入的參數個數就不包括參數了。同理,rest參數也不會計入length屬性。

(function (...args){}).length //0
如果設置了默認值的參數不是尾參數,那么length屬性也不再計入后面的參數。
(function (a = 0,b,c){}).length //0
(function (a,b = 1,c){}).length //0

7.1.5 作用域

一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域(context)。等到初始化結束,這個作用域就會消失。這種語法行為在不設置參數默認值時是不會出現的。

var x = 1;
function f(x,y = x){
console.log(y);
}
f(2) //2
上面的代碼中,參數y的默認值等于變量x。調用函數f時,參數形成一個單獨的作用域。在這個作用域里面,默認值變量x指向第一個參數x,而不是全局變量x,所以輸出是2.
再看下面的例子。
let x= 1;
function f(y = x){
let x = 2;
console.log(y);
}
f()//1
上面的代碼中,函數f調用時,參數y = x 形成一個單獨的作用域。在這個作用域里面,變量x本身沒有定義,所以指向外層的全局變量x。函數調用時,函數體內部的局部變量x影響不到默認值變量x。
如果此時全局變量x不存在,就會報錯。
function f(y = x){
let x= 2;
console.log(y);
}
f() // ReferenceError
像下面這樣寫,也會報錯
var x = 1;
function foo(x = x){
//...
}
foo() //ReferenceError
上面的代碼中,參數x=x 形成一個單獨作用域,實際執行的是let x = x。由于暫時性死區,這行這行代碼會產生“定義”錯誤。

如果參數的默認值是一個函數,該函數的作用域也遵守這個規則。請看下面的例子。

let foo = 'outer';function bar(func = x =>foo){let foo = 'inner';console.log(func());
}
bar(); // outer
上面代碼中,函數bar的參數func的默認值是一個匿名函數,返回值為變量foo。函數參數形成的單獨作用域里面并沒有定義變量foo,所以foo指向外層的全局變量foo,因此輸出outer如果寫成下面這樣就會報錯。
function bar(func = ()=>foo){let foo = 'inner';console.log(func());
}
bar() //ReferenceError
上面的代碼中,匿名函數里面的foo指向函數外層,但是函數外層并沒有聲明變量foo,所以報錯。下面是一個更復雜的例子。var x= 1;function foo(x,y = function(){ x = 2;}){var x = 3y();console.log(x);}foo() //3x // 1
上面的代碼中,函數foo的參數形成一個單獨作用域。這個作用域中首先聲明了變量x,然后聲明了變量y。y的默認值是一個匿名函數,這個匿名函數內部的變量x指向同一個作用域的第一個參數x。函數foo內部又聲明了一個內部變量x,該變量與第一個參數x由于不是同一個作用域,所以不是同一個變量,因此執行y后,內部變量x和外部全局變量x的值都沒變。如果將var x = 3var去除,函數foo 的內部變量x就指向第一個參數x,與匿名函數內部的x 是一致的,所以最后輸出的就是2,而外層的全局變量x依然不受影響。var x = 1;function foo(x, y = function(){ x = 2;}) {x = 3;y();console.log(x);}foo() //2x // 1

7.1.6 應用

利用參數默認值可以指定某一個參數不得省略,如果省略就拋出一個錯誤。

function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(musBeProvied = throwIfMissing()){return mustBeProvided;}
foo()//Error:Missing parameter
如果調用的時候沒有參數,以上代碼中的foo函數就會調用默認值throwIfMissing函數,從而拋出一個錯誤。從上面的代碼還可以看到,參數mustBeProvided的默認值等于throwIfMissing函數的運行結果(即函數名之后有一對圓括號),這表明參數的默認值不是定義是執行,而是在運行時執行。如果參數已經賦值,默認值中的函數就不會運行。另外,可以見參數默認值設為undefined,這表明這個參數是可以省略的。
function foo(optional = undefined) { ... }

7.2 rest參數

ES6引入了rest參數(形式為“…變量名”),用于獲取函數的多余參數,這樣就不需要使用arguments對象了。rest參數搭配的變量是一個數組,該變量將多余的參數放入其中。

function add(...values){let sun = 0;for(var val of values) {sum += val;}return sunm;
}
add(2,5,3) //10以上代碼中的add函數是一個求和函數,利用rest參數可以向該函數傳入任意數目的參數。下面是一個rest參數代替arguments變量的例子。//arguments變量的寫法
function sortNumbers() {return Arrayt.prototype.slice.call(arguments).sort();
}
//rest 參數的寫法
const sortNumbers = (...numbers) => numbers.sort();
比較上面的兩種寫法可以發現,rest參數的寫法更自然也更簡潔。

rest參數中的變量代表一個數組,所以數組特有的方法都可以用這個變量。下面是一個利用rest參數改寫數組push方法的例子。

function push(array,...items) {items.forEach(function(item) {array.push(item);console.log(item);{);
}var a = [];
push(a,1,2,3);*注意!*
rest參數之后不能再有其他參數(即只能是最后一個參數)否則會報錯。
function f(a,...b,c) {
//...
}
//報錯
函數的length屬性不包括rest參數。
(function(a){}).length //1
(function(...a){}).length //0
(function(a,...b){}).length //0

7.3 嚴格模式

從ES5開始,函數內部可以設定為嚴格模式。

function doSomething(a,b) {'use strict'//code
}
ES2016做了一點修改,規定只要函數參數使用了默認值,解構賦值,或者拓展運算符,那么函數內部就顯示設定為嚴格模式,否則就會報錯。
//報錯
function doSomething(a,b = a) {'use strict';//code
}
//報錯
const doSomething = function ({a,b}) {'use strict';//code
}
//報錯
const doSomething = (...a) => {'use strict';//code
}
//報錯
const obj = {
doSomething({a,b}) {'use strict';//code
}
};
這樣規定的原因是,函數內部的嚴格模式同時適用于函數圖和函數參數。但是,函數執行時,先執行函數參數,然后再執行函數體,這樣就有一個不合理的地方;只有從函數體之中才能知道參數是否應該為嚴格模式執行,但是參數卻應該先于函數體執行。
//報錯
function doSomething(value = 070) {'use strict';return value;
}上面的代碼中,餐宿value的默認值是八進制數070,但是嚴格模式下不能用前綴0表示八進制,所以應該報錯,但是實際上,JavaScript引擎會先成功執行value = 070,然后進入函數體內部,發現需要嚴格模式執行時才會報錯。雖然可以先解析函數體代碼,在執行參數代碼,但是這樣無疑增加了復雜性。因此,標準索性禁止了這種用法,只要參數使用了默認值,解構賦值,拓展運算符,就不能顯式指定嚴格模式。有兩種方法可以規避這種限制。第一種是設定全局性嚴格模式,這是合法的。'use struct';function doSomething(a,b = a) {//code}第二種是把函數包在一個無參數的立即執行函數里面。
const doSomething = (function() {'use strict'return function(value = 42) {return value;};{());

7.4 name屬性

函數的name屬性返回該函數的函數名。
function foo(){}
foo.name //"foo"

這個屬性早就被瀏覽器廣泛支持,但是直到ES6才寫入了標準。
需要注意的是,ES6對這個屬性的行為做出了一些修改。如果將一個匿名函數賦值給一個變量,ES5的name屬性會返回空字符串,而ES6的name屬性會返回實際的函數名。

var f = function() {};// ES5
f.name //""
//ES6
f.name //"f"上面代碼中,變量func1等于一個匿名函數,ES5ES6的name屬性返回的值不一樣。如果將一個劇名函數賦值給一個變量,則ES5ES6的name屬性都返回這個具名函數原本的名字
const bar = function baz() {};
// ES5
var.name //"baz"//ES6 
bar.name //"baz"
Function 構造函數返回的函數實例,name屬性的值為anonymous。
(new Function).name //"anonymous"
bind返回的函數,name屬性值會加上bound前綴。function foo() {};foo.bind({}).name //"bound foo"
(function(){}).bind({}).name //"bound"

7.5 箭頭函數

7.5.1 基本用法
ES6允許使用“箭頭”(=>) 定義函數。

var f = v=> v;上面的箭頭函數等同于以下代碼。
var f = function(v) {
return v;
};如果箭頭函數不需要參數或需要多個參數,就使用圓括號代表參數部分。
var f = () => 5;
//等同于
var f = function() {retrun s};var sum = (num1,num2) => num1+num2;
//等同于
var sum = function(num1,num2) {return num1 + num2;
};如果箭頭函數的代碼塊部分多余一條語句,就要使用大括號將其括起來,并使用retrun語句返回。
var sun = (num1,num2) => {return num1 + num2;}由于大括號被解釋為代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號
var getTempItem = id =>({ id: id,name:"Temp });箭頭函數可以與變量解構結合使用
const full = ({ first,last}) => first +' ' + last;
//等同于 
function full(person) {return person.first + ' ' + person.last;
}箭頭函數使得表達式更簡潔。
const isEven = n => n%2 == 0;
const square = n =>n*n;上面的代碼只用了兩行就定義了兩個簡單的工具函數,如果不用箭頭函數,可能就要占用多行,而且還不如現在這樣寫醒目。箭頭函數的另一個用處簡化回調函數。//正常函數寫法
[1,2,3].map(function (x) {return x * x;});
// 箭頭函數的寫法
[1,2,3].map(x => x * x);
下面是另一個例子。//正常函數寫法
var result = values.sort(function (a,b) {return a - b;})
// 箭頭函數的寫法
var result = values.sort*(a,b) => a - b);
下面是rest參數與箭頭函數結合的例子。const numbers = (...nums) => nums;number(1,2,3,4,5)
//[1,2,3,4,5]const headAndTail = (head, ...tail) => [head,tail];headAndTail(1,2,3,4,5)
//[1,[2,3,4,5]]

7.5.2 注意事項

箭頭函數有以下幾個使用注意事項。
  1. 函數體內的this對象就是定義是所在的對象,而不是使用時所在的對象。
  2. 不可以當作構造函數。也就是說,不可以使用new命令,否則會拋出一個錯誤
  3. 不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用rest參數代替。
  4. 不可以使用yield命令,因此箭頭函數不能用作Generatot函數。
    其中一點尤其值得注意。this對象的指向是可變的,但箭頭函數中它是固定的,
function foo() {setTimeout(() =>{console.log('id',this.id);},100);}
var id = 21;
foo.call({ id : 42});
//id:42上面的代碼中,setTimeout的參數是一個箭頭函數,這個箭頭函數的定義是在foo函數生成時生效的,而他真正執行要等到100ms后。如果是普通函數,執行時this應該指向全局對象window,這時應該輸出21.但是,箭頭函數導致this總是指向函數定義生效時所在的對象(本例是{id:42}),所以輸出的是42.箭頭函數可以讓setTimeout里面的this綁定定義時所在的作用域,而不是指向運行時所在的作用域。下面是另一個例子。
function Timer() {this.s1 = 0;this.s2 = 0;
//箭頭函數
setInterval{() => this.s1++,1000);
//普通函數
setInterval(function () {this.s2++;
},1000);
}var timer = new Timer();setTimeout(() => console.log('s1: ',timer.s1),3100);setTimeout(() => console.log('s2: ',timer.s2),3100);// s1:3//s2:0上面的代碼中,Timer函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數,前者的this綁定定義是所在的作用域(即Timer函數),后者的this指向運行時所在的作用域(即全局對象)。所以3100ms之后,thmer.s1被更新了3次,而thimer。說一次都沒更新。箭頭函數可以讓this指向固定化,這種特性非常有利于封裝回調函數。假面是一個例子。DOM事件的回調函數封裝在一個對象里面。var handler = {id:'123456',init:function() {document.addEventListener('click,event => this.doSomething(event.type),false);},doSomething:function(type){console.log('Handing' + type +'for' + this.id);}};以上的代碼的init方法中使用了箭頭函數,這導致箭頭函數里面的this總是指向handler對象。否則,回調函數運行時,this.doSomething一行會報錯,因為此時this指向document對象。this指向的固定化并不是因為箭頭函數內部有綁定this的機制,實際原因是因為箭頭函數根本沒有自己的this,導致內部的this就是外層代碼塊的this。正是因為他沒有this,所以不能用作構造函數。箭頭函數轉成ES5的代碼如下。
//ES5
function foo() {setTimeout(() => {console.log('id:',this.id);},100);
}//ES5
function foo() {var _this = thislsetTimeout(function () {console.log('id:',_this.id);},100);
}上面的代碼中,轉換后的ES5版本清楚地說明了箭頭函數里面根本沒有自己的this,而是引用外層的this。請問下面的代碼之中有幾個thisfunction foo() [return ()=> {retrun () => {retrun () => {console.log('id:',this.id);};};};}var f = foo.call({id:1});var t1 = f.call({id:2})()();// id:1var t2 = f().call({id:3})();// id:1var t3 = f()().call({id:4}); // id:1上面的代碼中只有一個this,就是函數foo的this,所以t1,t2,t3都輸出同樣的結果。因為所有的內層函數都是箭頭函數,都沒有自己的this,他們的this其實就是最外層foo函數的this。除了this,以下3個變量在箭頭函數中也是不存在的,分別指向外層函數的對應變量:arguments,supernew.targetfunction foo() {setTimeout(() => {console.log('args:',arguments);},100);}
foo(2,4,5,8)
//args:[2,4,6,8]上面的代碼中,箭頭函數內部的變量arguments其實就是函數foo的arguments變量。另外,由于箭頭函數沒有自己的this,當然也就不用call(),apply(),bind()這些方法去改變this的指向。
(function() {retrun [(() => this.x).bind({ x: 'inner' })()];}).call({ x:'outer'});//['outer']上面代碼中,箭頭函數沒有自己的this,所以bind方法無效,內部的this只想外部的this。長期以來,JavaScript 語言的this對象一直是一個令人頭痛的問題,在對象方法中使用this必須非常小心。箭頭函數“綁定”this,很大程度上解決了這個困擾。

7.5.3 嵌套的箭頭函數

箭頭函數內部還可以在使用箭頭函數。下面是一個ES5語法的多重嵌套函數。
function insert(value) {return {into:function (array) {return {after:function  (afterValue) }array.splice(arrat.indexOf(afterValue) + 1,0 value);return array;}};}};}
insert(2).into([1,3]).after(1); //[1,2,3]

上面這個函數可以使用箭頭函數改寫如下。

let insert = (value) => ({into: (array) => ({after: (afterValue) => {array.splice(array.indexOf(afterValue) +1,0,value);return array;}})});
insert(2).into([1,3]).after(1);//[1,2,3]
下面是一個部署管道(pipeline)的例子,即前一個函數的輸出時候一個函數的輸入。const pipeline = (...funcs) => 
val => funcs.reduce((a,b) =>b(a),val);const plus1 = a =>a+1;
const mult2 = a =>a * 2;
const addThenMult  = pipeline(plus1,mult2);addThenMult(5)
//12
如果覺得上面的寫法可讀性比較差,也可以采用下面的寫法
const plus1 = a => a + 1;
const mult2  = a => a * 2;mult2(plus1(5))
//12

7.6 綁定this

箭頭函數可以綁定this對象,大大減少了顯式綁定this對象的寫法(call,apply,bind).但是,箭頭函數并非適用于所有場合,所以ES7提出“函數綁定”(function bind )運算符,用來取代call,apply,bind調用。
函數綁定運算符是并排的雙冒號( ::),雙冒號左邊是一個對象,右邊是一個函數。該運算符會自動將左邊的對象作為上下文環境(即this對象)綁定到右邊函數上

	foo::bar;//等同于bar.bind(foo)foo::bar(...arguments);//等同于bar.apply(foo,arguments);
const hasOwnPropery = Object.prototype.hasOwnProperty;
function hasOwn(obj,key) {return obj::hasOwnProperty(key);
}

如果雙冒號左邊為空,右邊是一個對象的方法,則等于將該方法綁定在該對象上。

var method = obj::obj.foo;
//等同于
var method = ::obj.foo;let log = ::console.log;
//等同于
var log = console.log.bind(console);

由于雙冒號運算符返回的還是原對象,因此可以采用鏈式寫法。

//列一
import { map.takeWhile,forEach } from "iterlib";getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x = > console.log(x));// 列二
let {find,html } = jake;document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha"):

7.7 尾調用優化

7.7.1 什么是尾調用

尾調用(Tail Call)是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數的最后一步是調用另一個函數。

function f(x){return g(x);}
上面的代碼中,函數f的最后一步是調用函數g,這就叫做尾調用。
以下情況都不屬于尾調用。//情況一
function f(x) {let y = g(x);return y;
}//情況二
function f(x) {return g(x) +1;
}//情況三
function f(x) {g(x);
}上面的代碼中,情況一是調用函數g之后還有賦值操作,所以不屬于未調用,即使語義完全一樣,情況二也屬于調用后還有操作,即使寫在一行內,情況三等同于下面的代碼。function f(x) {g(x);return undefined;
}尾調用不一定出現在函數尾部,只要是最后一步操作即可。
function f(x) {if(x > 0) {return m(X)}retrun n(x);
}
上面的代碼中,函數m和n都屬于尾調用,因為他們都是函數f的最后一步操作。

7.7.2 尾調用優化
尾調用之所以與其他調用不同,就在于其特殊的調用位置。
我們知道,函數調用會在內存形成一個**“調用記錄”又稱“調用幀”**,保存調用位置和內存變量等信息。如果在函數A的內部調用函數B,那么在A的調用幀上方還會形成一個B的調用幀,等到B運行結束,將結果返回到A,B的調用幀才會消失。如果函數B內部還調用函數C,那就還有一個C的調用幀,以此類推。所有的調用幀就形成一個“調用棧“
尾調用由于是函數的最后一步操作,所以不需要保留外層函數的調用幀,因為調用位置內部變量等信息都不回再用到了,直接用內層函數的調用幀取代外層函數即可。

function f() {let m = 1;let n = 2;return g(m + n);
}
f();//等同于
function f() {return g(3);
}f();
//等同于
g(3);
上面的代碼中,如果函數g不是尾調用,函數f就需要保存內存變量m和n的值,g的調用位置等信息。但是由于調用g之后,函數f就結束了。所以執行到最后一步,完全可以刪除f(x)的調用幀,只保留g(3)的調用幀,
這就叫做“尾調用優化”,即只保留內層函數的調用幀,如果所有的函數都是尾調用,那么完全可以做到每次執行時調用幀只有一項,這將大大節省內存,這就是“尾調用優化”的意義。
**注意**
只有不在用到外層函數的內部變量,內層函數的調用幀才回取代外層函數的調用幀,否則就無法進行“尾調用優化”.
function addDne(a){var one = 1;funciton inner(b) {return b+one;}return inner(a);
}
上面的函數不會進行尾調用優化,因為內層函數inner用到了外層函數addOne的內部變量one。

7.7.3 尾遞歸

函數調用自身稱為遞歸,如果尾調用自身就稱尾遞歸。
遞歸非常耗費內存,因為需要同時保存成百上千個調用幀。很容易發生棧溢出錯誤。但對于尾遞歸來說由于只存在一個調用幀,所以永遠不會發生“棧溢出”錯誤。
function factcrial(n) {if(n=== 2) return 1;return n * factoral(n - 1);
}
factorial(5)// 120上面的代碼是一個階乘函數,計算n的階乘,最多需要保存n個調用記錄,復雜度尾O(n).
如果改寫成尾調用遞歸,只保留一個調用記錄,則復雜度尾O(n);function factorial (n,total) {if (n === 1) return total;return factorial(n = 1,n* total);
}
factorial(5,1) //120

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15088.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15088.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15088.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

UE5 雙手握劍的實現(逆向運動學IK)

UE5 雙手握劍的實現 IK 前言 什么是IK? UE官方給我們提供了很多對于IK處理的節點,比如ABRIK、Two Bone IK、Full Body IK 、CCD IK等,但是看到這,很多人就好奇了,什么是IK? 首先我們來看看虛幻小白人的骨…

[圖解]產品經理創新之阿布思考法

0 00:00:00,000 --> 00:00:01,900 那剛才我們講到了 1 00:00:02,730 --> 00:00:03,746 業務序列圖 2 00:00:03,746 --> 00:00:04,560 然后怎么 3 00:00:05,530 --> 00:00:06,963 畫現狀,怎么改進 4 00:00:06,963 --> 00:00:09,012 然后改進的模式…

【Spring Security + OAuth2】授權

Spring Security OAuth2 第一章 Spring Security 快速入門 第二章 Spring Security 自定義配置 第三章 Spring Security 前后端分離配置 第四章 Spring Security 身份認證 第五章 Spring Security 授權 第六章 OAuth2 文章目錄 Spring Security OAuth21、基于request的授權1…

一條命令安裝Metasploit Framework

做安全滲透的人都或多或少的使用kali-Linux系統中msfconsole命令啟動工具,然而也經常會有人遇到這樣那樣的問題無法啟動 今天我們就用一條命令來重新安裝這個工具 curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/met…

AI學習AI知識路線

數學基礎 一、數據分析 二、概率論 三、線性代數及矩陣 l 數學基礎 1)常數e2)導數3)梯度 4)Taylor5)gini系數6)信息熵與組合數 1)概率論基礎2)古典模型3)常見概率分布 4)大數定理和中心極限定理5)協方差(矩陣)和相關系數 6)最大似然估計和最大后驗估計 1)線性空間及線性變…

Windows內核--內存區對象(Section Object)(5.2)

內存區對象 Section Object表示可以共享的內存段。進程可以使用Section與其他進程共享其部分內存地址空間. Section還可為進程提供將文件映射到其內存地址空間的機制。 Linux有mmap與之類似。 參考: Section Objects and Views 內存區對象是虛擬描述符表VAD節點的一種 VAD樹節點…

LabVIEW如何確保自動化設備的穩定性和可靠性?

為了確保LabVIEW在自動化設備中的穩定性和可靠性,可以采取以下關鍵措施: 1. 代碼架構與設計 模塊化設計:將程序分解為獨立的模塊或子VI,每個模塊負責特定功能,便于測試和維護。狀態機架構:使用狀態機架構…

zookeeper選主之LeaderLatch

概述 利用zookeeper來進行選主,可以使用apache curator framework,它給我們封裝了兩種選主工具,它們分別是LeaderSelector和LeaderLatch。它們各自的應用場景不一樣,LeaderSelector應用于那些需要頻繁變主的情況,而Le…

Redis機制-Redis互斥鎖、分布式鎖

目錄 一 互斥鎖 二 分布式鎖 Redis實現分布式鎖 redisson實現分布式鎖 可重入性: 主從一致性(性能差): 一 互斥鎖 假設我們現在有一個業務要實現秒殺優惠券的功能,如果是一個正常的流程,線程之間應該…

數據結構中鏈表的題目

題目: 設計一個算法,要求將鏈表中所有節點的鏈接方向“原地”逆轉,即要求僅利用原表的存儲空間。 對于這個問題,首先要分析的是:鏈表中的頭和尾節點如何插入?其次就是:如何鏈接? 搞懂…

閱讀筆記——《未知協議狀態機推斷技術研究綜述》

【參考文獻】盛嘉杰, 牛勝杰, 陳陽, 等. 未知協議狀態機推斷技術研究綜述[J]. 計算機與現代化, 2023 (05): 58.【注】本文僅為作者個人學習筆記,如有冒犯,請聯系作者刪除。 摘要 協議逆向工程(PRE)描述了協議的行為邏輯&#xff…

spring cloud config server源碼學習(一)

文章目錄 1. 注解EnableConfigServer2. ConfigServerAutoConfiguration2.1 ConditionalOnBean和ConditionalOnProperty2.2 Import注解2.2.1. EnvironmentRepositoryConfiguration.class2.2.2. CompositeConfiguration.class2.2.3. ResourceRepositoryConfiguration.class2.2.4.…

python3 + selenium webdriver自動化測試啟動不同瀏覽器

selenium webdriver自動化測試啟動不同瀏覽器 selenium webdriver 介紹Selenium WebDriver 進行自動化測試的一般流程瀏覽器驅動下載瀏覽器驅動的安裝chrome、edge、Firefox、Opera、Safari、phantomjs 應用Headless Chrome 、Headless Firefox 應用 selenium webdriver 介紹 …

shell命令運行原理及Linux權限問題

目錄 shell命令以及運行原理用戶管理添加用戶刪除用戶sudo Linux權限的概念Linux權限管理文件訪問者的分類(人)文件類型和訪問權限(事物屬性)文件權限值的表示方法文件訪問權限的相關設置方法 目錄的權限粘滯位 shell命令以及運行…

備考AMC8和AMC10競賽,吃透2000-2024年1850道真題和解析(持續)

多做真題,吃透真題和背后的知識點是備考AMC8、AMC10有效的方法之一,通過做真題,可以幫助孩子找到真實競賽的感覺,而且更加貼近比賽的內容,可以通過真題查漏補缺,更有針對性的補齊知識的短板。 今天我們繼續…

PostgreSQL基本使用Schema

參考文章:PostgreSQL基本使用(3)Schema_pg數據庫查詢schema-CSDN博客 PostgreSQL 模式(Schema)可以理解為是一個表的集合(或者所屬者)。 例如:在 MySQL 中,Scheam 是庫&…

gcc源碼分析(AST抽象語法樹)

文章目錄 三、AST相關1、AST(抽象語法樹)1.1 樹結點的聲明1.2 樹結點的結構1.2.1 tree_node聯合體1.2.2 tree_base結構體1.2.3 tree_common結構體1.2.4 常量結構體1.2.5 **標識符節點**2、符號綁定,作用域與block樹節點2.1 lang_identifier結構體2.2 c_binding結構體2.3 scop…

HLS視頻加密,讓您的視頻內容更安全!

背景介紹 HLS視頻加密是一種基于HTTP Live Streaming(HLS)協議的加密技術。它的核心思想是將視頻切片進行加密處理,在客戶端播放時需要先獲取解密密鑰才能正常偶發。通過這種方式,HLS加密可以有效防止未經授權的第三方竊取視頻內…

測試短信推薦參考

短信測試參考 國外: smstome 支持多個國家號碼 官網地址: https://smstome.com/ quackr.io 支持多個國家號碼 官網地址: https://quackr.io/ receive-smss 支持多個國家號碼 地址: https://receive-smss.com/ receive-sms-fr…

C#字典的常用方法

C#的字典(Dictionary)類是一個通用的集合類,它實現了鍵值對的存儲和訪問。以下是一些常用的字典方法: Add(key, value):向字典中添加一個指定的鍵值對。Remove(key):從字典中移除具有指定鍵的元素。Contai…