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