JS函數(shù)

1、函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別

  • 函數(shù)聲明可以看作是函數(shù)的初始化,我們將給函數(shù)傳參并建立函數(shù)體的表達(dá)式,當(dāng)我門建立完成后,就可以運(yùn)行行函數(shù)的表達(dá)式了,做法如下:function foo(){}, foo();
  • 函數(shù)表達(dá)式其實(shí)就是創(chuàng)建一個(gè)匿名的函數(shù)聲明并賦值給一個(gè)變量,如var foo = function () {}。
  • 使用函數(shù)聲明的時(shí)候,其定義的foo函數(shù)名會(huì)受到j(luò)avascript的變量名提升機(jī)制的影響,而通過函數(shù)表達(dá)式創(chuàng)建的函數(shù),當(dāng)然其最明顯直接的特征就是省略了函數(shù)名.
  • 另外還有幾種函數(shù)的聲明寫法,比如自執(zhí)行函數(shù),主要用于創(chuàng)建一個(gè)新的作用域,在此作用域內(nèi)聲明的變量不會(huì)和其它作用域內(nèi)的變量沖突或混淆,大多是以匿名函數(shù)方式存在,且立即自動(dòng)執(zhí)行,如:(function(){var x = xx; return x}),還有就是一些js的函數(shù)設(shè)計(jì)模式,比如構(gòu)造,工廠,混合,等....

2、什么是變量的聲明前置?什么是函數(shù)的聲明前置

  • 變量提升:當(dāng)一個(gè)變量被定義時(shí),在代碼執(zhí)行前會(huì)先將變量進(jìn)行初始化(提升到當(dāng)前作用域頂部)再執(zhí)行語句。
console.log(a)  //undefined
var a=1
console.log(a)  //1
  • 函數(shù)提升:當(dāng)函數(shù)以函數(shù)聲明的方式聲明時(shí),代碼執(zhí)行前會(huì)首先生成該函數(shù)(提升到當(dāng)前作用域頂部),然后再執(zhí)行語句
fn('hello')  //"hello"
function fn(str){
  console.log(str)
}

3、arguments 是什么

  • 函數(shù)聲明或函數(shù)表達(dá)式中,如function fn(v1,v2){...}中,v1、v2是函數(shù)的形參,而在實(shí)際調(diào)用時(shí)傳入的參數(shù)會(huì)存入arguments中,如fn(3,4,5),那么arguments的長度就是3,分別對應(yīng)arguments[0]:3,arguments[1]:4,arguments[2]:5。
  • arguments是類數(shù)組對象,每個(gè)函數(shù)中都存在argument對象,argument并不是一個(gè)真正的數(shù)組,所以不具備除length屬性之外的屬性,這個(gè)對象維護(hù)這所有傳入該函數(shù)的參數(shù)列表。
    通過以下語句可將arguments轉(zhuǎn)化為數(shù)組對象
var args=Array.prototype.slice.call(arguments)

4、函數(shù)的"重載"怎樣實(shí)現(xiàn)

  • 重載是很多面向?qū)ο笳Z言實(shí)現(xiàn)多態(tài)的手段之一,在靜態(tài)語言中確定一個(gè)函數(shù)的手段是靠方法簽名——函數(shù)名+參數(shù)列表,也就是說相同名字的函數(shù)參數(shù)個(gè)數(shù)不同或者順序不同都被認(rèn)為是不同的函數(shù),稱為函數(shù)重載。
  • 在JavaScript中沒有函數(shù)重載的概念,函數(shù)通過名字確定唯一性,參數(shù)不同也被認(rèn)為是相同的函數(shù),后面的覆蓋前面的,但可以在函數(shù)體針對不同的參數(shù)調(diào)用執(zhí)行相應(yīng)的邏輯。
function printPeopleInfo (name, age, sex){
    if(name){
      console.log(name);
    }
    if(age){
      console.log(age);
    }
    if(sex){
      console.log(sex);
    }
  }
  printPeopleInfo('Byron', 26);
  printPeopleInfo('Byron', 26, 'male');

5、立即執(zhí)行函數(shù)表達(dá)式是什么?有什么作用

我們都知道,一般定義一個(gè)函數(shù)有函數(shù)聲明和函數(shù)表達(dá)式兩種方法:
function fnName () {…};   //函數(shù)聲明
var fnName = function () {…};   //函數(shù)表達(dá)式
兩者的區(qū)別是:

  • Javascript引擎在解析javascript代碼時(shí)會(huì)‘函數(shù)聲明提升'(Function declaration Hoisting)當(dāng)前執(zhí)行環(huán)境(作用域)上的函數(shù)聲明,而函數(shù)表達(dá)式必須等到Javascirtp引擎執(zhí)行到它所在行時(shí),才會(huì)從上而下一行一行地解析函數(shù)表達(dá)式。
  • 函數(shù)表達(dá)式后面可以加括號立即調(diào)用該函數(shù),函數(shù)聲明不可以,只能以fnName()形式調(diào)用 。

所以,要在函數(shù)體后面加括號就能立即調(diào)用,則這個(gè)函數(shù)必須是函數(shù)表達(dá)式,不能是函數(shù)聲明。

在function前面加()、!、+、-、=等運(yùn)算符,都將函數(shù)聲明轉(zhuǎn)換成函數(shù)表達(dá)式,消除了javascript引擎識別函數(shù)表達(dá)式和函數(shù)聲明的歧義,告訴javascript引擎這是一個(gè)函數(shù)表達(dá)式,不是函數(shù)聲明,可以在后面加括號,并立即執(zhí)行函數(shù)的代碼。

(function(){
  console.log(123)
})()         //輸出123

(function(){
  console.log(123)
}())         //輸出123

!function(){
  console.log(123)
}()         //輸出123

+function(){
  console.log(123)
}()         //輸出123

-function(){
  console.log(123)
}()         //輸出123

var a=function(){
  console.log(123)
}()         //輸出123

加括號是最安全的做法,因?yàn)?!?、-等運(yùn)算符還會(huì)和函數(shù)的返回值進(jìn)行運(yùn)算,有時(shí)造成不必要的麻煩。

那么這樣做有什么作用:
在全局或局部作用域中聲明了一些變量,可能會(huì)被其他人不小心用同名的變量給覆蓋掉,根據(jù)javascript函數(shù)作用域鏈的特性,可以使用這種技術(shù)可以模仿一個(gè)私有作用域,用匿名函數(shù)作為一個(gè)“容器”,“容器”內(nèi)部可以訪問外部的變量,而外部環(huán)境不能訪問“容器”內(nèi)部的變量,所以( function(){…} )()內(nèi)部定義的變量不會(huì)和外部的變量發(fā)生沖突,俗稱“匿名包裹器”或“命名空間”。

6、求n!,用遞歸來實(shí)現(xiàn)

利用n!等于n(n-1)!,(n-1)!等于(n-1)((n-1)-1)!,直至括號內(nèi)的值為1,另外0!等于1。

function fac(n){
  if( n===1 || n===0 ){return 1}
  return (n*fac(n-1))
}
console.log(fac(1))     //輸出1
console.log(fac(2))     //輸出2
console.log(fac(3))     //輸出6
console.log(fac(4))     //輸出24
console.log(fac(5))     //輸出120

也可以用三元運(yùn)算符 ' ? ' 寫作:

function fac(n){
  return n===0 || n===1 ? 1 : n*fac(n-1)
}
console.log(fac(5))     //輸出120

另外也可以利用循環(huán)來處理

function fac(n){
  var i =1
  if( n===1 || n===0 ){console.log(1)}
  else{
    for( var j=n;j>1;j--){
      i*=j
    }
    console.log(i)
  } 
}
fac(5)     //輸出120

7、以下代碼輸出什么?

function getInfo(name, age, sex){
        console.log('name:',name);
        console.log('age:', age);
        console.log('sex:', sex);
        console.log(arguments);
        arguments[0] = 'valley';
        console.log('name', name);
    }

getInfo('饑人谷', 2, '男');
getInfo('小谷', 3);
getInfo('男');

注意:

  • console.log(…)輸出的并不是括號內(nèi)的返回值,而是輸出括號內(nèi)所有表達(dá)式的值。
  • 給函數(shù)傳入?yún)?shù)時(shí)是按順序傳入,不會(huì)自動(dòng)識別,沒有傳入?yún)?shù)則為undefined。
    所以getInfo('饑人谷', 2, '男');相當(dāng)于:
function getInfo(){
            arguments[0]='饑人谷'
            arguments[1]=2
            arguments[2]='男'
            console.log('name:','饑人谷')     
            console.log('age:', 2)
            console.log('sex:', '男')
            console.log(['饑人谷',2,'男'])
            arguments[0] = 'valley'
            console.log('name', 'valley')
}
getInfo()

/*輸出:
name: 饑人谷
age: 2
sex: 男
["饑人谷", 2, "男"]
name valley
*/

getInfo('小谷', 3);相當(dāng)于

function getInfo(){
            arguments[0]='饑人谷'
            arguments[1]=3
            arguments[2]=undefined
            console.log('name:','饑人谷')     
            console.log('age:', 3)
            console.log('sex:', undefined)
            console.log(['饑人谷',3])
            arguments[0] = 'valley'
            console.log('name', 'valley')
}
getInfo()

/*輸出:
name: 饑人谷
age: 3
sex: undefined
["饑人谷", 3]
name valley
*/

getInfo('男');相當(dāng)于

function getInfo(){
            arguments[0]='男'
            arguments[1]=undefined
            arguments[2]=undefined
            console.log('name:','男')     
            console.log('age:', undefined)
            console.log('sex:', undefined)
            console.log(['男'])
            arguments[0] = 'valley'
            console.log('name', 'valley')
}
getInfo()

/*輸出:
name: 男
age: undefined
sex: undefined
["男"]
name valley
*/

8、 寫一個(gè)函數(shù),返回參數(shù)的平方和?

function sumOfSquares(){
   }
   var result = sumOfSquares(2,3,4)
   var result2 = sumOfSquares(1,3)
   console.log(result)  //29
   console.log(result2)  //10

解答思路:遍歷每一個(gè)傳入的參數(shù),求它們的平方和;通過不同遍歷的方法,有不同的寫法。

  • for循環(huán)方法

循環(huán)每執(zhí)行一次,都要檢查一次 array.length 的值,讀屬性要比讀局部變量慢,尤其是當(dāng) array 里存放的都是 DOM 元素(像 array = document.getElementByClassName(“class”);),因?yàn)槊看巫x array.length 都要掃描一遍頁面上 class=”class” 的元素,速度更是慢得抓狂。

function sumOfSquares(){
    var sum=0
    for(var i=0;i<arguments.length;i++){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
var result = sumOfSquares(2,3,4)
var result2 = sumOfSquares(1,3)
console.log(result)  //29
console.log(result2)  //10
  • for-in循環(huán)方法

for-in 需要分析出 array 的每個(gè)屬性,這個(gè)操作的性能開銷很大

function sumOfSquares(){
    var sum=0
    for(i in arguments){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
  var result = sumOfSquares(2,3,4)
  var result2 = sumOfSquares(1,3)
  console.log(result)  //29
  console.log(result2)  //10
  • 先把數(shù)組的長度先查出來,存進(jìn)一個(gè)局部變量,那么循環(huán)的速度將會(huì)大大提高
function sumOfSquares(){
    var sum=0
    var length=arguments.length
    for(var i=0;i<length;i++){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
  var result = sumOfSquares(2,3,4)
  var result2 = sumOfSquares(1,3)
  console.log(result)  //29
  console.log(result2)  //10
  • 不過我們還可以讓它更快。如果循環(huán)終止條件不需要進(jìn)行比較運(yùn)算,那么循環(huán)的速度還可以更快
  • 把數(shù)組下標(biāo)改成向 0 遞減,循環(huán)終止條件只需要判斷 i 是否為 0 就行了。因?yàn)檠h(huán)增量和循環(huán)終止條件結(jié)合在一起,所以可以寫成更簡單的 while 循環(huán)
function sumOfSquares(){
    var sum=0
    var i=arguments.length
    while(i--){
        sum+=arguments[i]*arguments[i]
    }
    return sum
}
var result = sumOfSquares(2,3,4)
var result2 = sumOfSquares(1,3)
console.log(result)  //29
console.log(result2)  //10

9、如下代碼的輸出?為什么

console.log(a);
var a = 1;
console.log(b);

由于變量提升的原則,上述代碼相當(dāng)于

var a
console.log(a)    //輸出undefined
a=1
console.log(b)    //報(bào)錯(cuò):Uncaught ReferenceError: b is not defined
  • 原因:先聲明了變量a,a并沒有復(fù)制,所以此時(shí)輸出a得到undefined;b沒有聲明就直接調(diào)用,所以會(huì)報(bào)錯(cuò)。

10、如下代碼的輸出?為什么

sayName('world');
    sayAge(10);
    function sayName(name){
        console.log('hello ', name);
    }
    var sayAge = function(age){
        console.log(age);
    };

輸出:"hello" "world" Uncaught TypeError: sayAge is not a function

  • 原因:由于函數(shù)聲明會(huì)自動(dòng)提升,而函數(shù)表達(dá)式不會(huì);sayName('world');正常執(zhí)行;sayAge(10);會(huì)調(diào)用函數(shù)sayAge但此時(shí)只聲明了sayAge是變量,并未將函數(shù)聲明賦值給它,所以它還不是函數(shù),所以報(bào)錯(cuò)。

11、 如下代碼輸出什么? 為什么

var x = 10
bar() 
function foo() {
  console.log(x)
}
function bar(){
  var x = 30
  foo()
}

輸出:10

  • 原因:函數(shù)的作用域與其定義時(shí)所在的作用域有關(guān),與其調(diào)用時(shí)所在的作用域無關(guān);在bar()中可以調(diào)用全局作用域中的foo(),而foo()不能訪問bar()的局部變量x(x=30),只能訪問全局作用域中的全局變量x(x=10),所以輸出10。

12、如下代碼輸出什么? 為什么

var x = 10;
bar() 
function bar(){
  var x = 30;
  function foo(){
    console.log(x) 
  }
  foo();
}   

輸出:30

  • 原因:變量的查找是就近原則,去尋找var定義的變量,當(dāng)就近沒有找到的時(shí)候就去查找外層。函數(shù)域優(yōu)先于全局域,故局部變量x會(huì)覆蓋掉全局變量x,所以輸出30。

13、以下代碼輸出什么? 寫出作用域鏈的查找過程偽代碼

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function (){
    console.log(x)
  })()
}
//30

偽代碼如下:

globalContext = {
  AO: {
    x: 10
    bar: function
  }
  Scope: null
}
bar.[[scope]] = globalContext.AO

barContext = {
  AO: {
    x: 30
    (no-name): function
  }
  Scope: bar.[[scope]] //globalContext.AO
}
(no-name).[[scope]] = barContext.AO

(no-name)Context = {
  AO: {}
  Scope: (no-name).[[scope]] //barContext.AO
}

當(dāng)調(diào)用 (no-name)() 時(shí),先從 (no-name) 執(zhí)行上下文中的 AO里找,找不到再從 bar 的 [[scope]]里找,得到x=30
找到后即調(diào)用

14、以下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var a = 1;

function fn(){
  console.log(a)     //輸出undefined
  var a = 5
  console.log(a)     //輸出5
  a++
  var a
  fn3()
  fn2()
  console.log(a)     //輸出20
  function fn2(){
    console.log(a)     //輸出6
    a = 20
  }
}

function fn3(){
  console.log(a)     //輸出1
  a = 200
}

fn()
console.log(a)     //輸出200

偽代碼如下:

globalContext = {
  AO: {
    a: 1
    fn: function
    fn3: function
  }
  Scope: null
}
fn.[[scope]] = globalContext.AO
fn3.[[scope]] = globalContext.AO
//執(zhí)行fn()
fnContext = {
  AO: {
    a: undefined
    fn2: function
  }
  Scope:  fn.[[scope]] //globalContext.AO
}
//執(zhí)行console.log(a)     //輸出undefined
//a = 5
fnContext = {
  AO: {
    a: 5
    fn2: function
  }
  Scope:  fn.[[scope]] //globalContext.AO
}
fn2.[[scope]] = fnContext.AO
//執(zhí)行console.log(a)     //輸出5
//  a++
fnContext = {
  AO: {
    a: 6
    fn2: function
  }
  Scope:  fn.[[scope]] //globalContext.AO
}
fn2.[[scope]] = fnContext.AO
//執(zhí)行fn3()
fn3Context = {
  AO: {}
  Scope:  fn3.[[scope]] //globalContext.AO
}
//執(zhí)行console.log(a)     //輸出1
//a=200全局中a變?yōu)?00
//執(zhí)行fn2()
fn2Context = {
  AO: {}
  Scope:  fn2.[[scope]] //fnContext.AO
}
//執(zhí)行console.log(a)     //輸出6
//a=20fn中a變?yōu)?0
//執(zhí)行console.log(a)     //輸出20
//執(zhí)行console.log(a)     //輸出200

所以最終輸出

undefined
5
1
6
30
200
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 (*)解析器會(huì)率先讀取函數(shù)聲明,并使其在執(zhí)行任何代碼之前可以訪問;函數(shù)表達(dá)式則必須...
    coolheadedY閱讀 446評論 0 1
  • 1. 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 (*) 函數(shù)在JS中有三種方式來定義:函數(shù)聲明(function decla...
    進(jìn)擊的阿群閱讀 482評論 0 1
  • 概念 1、函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別? ECMAScript規(guī)定了三種聲明函數(shù)方式: 構(gòu)造函數(shù)首先函數(shù)也是對象...
    周花花啊閱讀 570評論 1 1
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,546評論 0 13
  • 1.函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 ? 2.什么是變量的聲明前置?什么是函數(shù)的聲明前置 js引擎的工作方式:先解析...
    饑人谷區(qū)子銘閱讀 490評論 0 1

友情鏈接更多精彩內(nèi)容