作用域
在JS中(非ES6),只有函數(shù)作用域,沒有塊作用域。
例如,for循環(huán),while等{}內(nèi)部的變量其實(shí)是和外部處于同一個(gè)作用域的:
for (var i =1; i < 5; i ++) {
var a = 3;
}
console.log(a); //3 此時(shí)沒有輸出undefined,說明a和for循環(huán)內(nèi)部的a是同一個(gè)作用域。
所以只有函數(shù)作用域:
function fn () {
var a = 1;
if (a > 2) {
var b = 3;
}
console.log(b);
}
fn();
console.log(a);
上述代碼的運(yùn)行結(jié)果等價(jià)于:
function fn () {
var a; // 進(jìn)行變量的提升
var b; // 定義b是在if的{}內(nèi)進(jìn)行的,但是沒有塊作用域,實(shí)質(zhì)上作用域還是fn內(nèi)部,a和b是同一個(gè)作用域
a = 1;
if (a > 2) { //a沒有滿足條件,所以不會執(zhí)行給b賦值的語句,所以n僅僅聲明了但是沒有賦值,console.log(b)的結(jié)果是undefined
b = 3;
}
console.log(b); //undefined
}
fn();
console.log(a); // a is not defined 報(bào)錯(cuò),因?yàn)樵诋?dāng)前的作用域即全局作用域中,a并沒有定義,a只在fn的作用域里定義了。
var
- var如果重復(fù)聲明一個(gè)已經(jīng)存在的變量時(shí),原來的變量的值是不會變的。
var a =1;
var a;
var a;
var a;
console.log(a); // 1 重復(fù)聲明不會改變。
- 不加var的作用
不寫var會聲明一個(gè)全局變量,所以不建議不寫var,即使需要全局變量,也要在全局作用域中使用var聲明變量。
function fn () {
a = 1; //沒有用var聲明,a其實(shí)是一個(gè)全局變量,在外部作用域中也能訪問
}
fn();
console.log(a);//1 說明全局作用域中也能訪問a
1.函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別
有三種聲明函數(shù)的方式:
- 構(gòu)造函數(shù):
var doSomething = new Function("console.log('hello,deejay')");不推薦使用 - 函數(shù)聲明:
function doSomething () { // 函數(shù)聲明
console.log('hello,deejay');
}
doSomething(); // 調(diào)用也可以放到聲明的前面
- 函數(shù)表達(dá)式:
var doSomething = function () { //函數(shù)表達(dá)式
console.log("hello,deejay");
}
doSomething(); // 表達(dá)式調(diào)用只能寫在賦值聲明后面
2.什么是變量的聲明前置?什么是函數(shù)的聲明前置
var和function的聲明前置:在一個(gè)作用域下,var聲明的變量和function聲明的函數(shù)會前置
console.log(a); // undefined
var a = 3;
console.log(a); // 3
sayHello();
function sayHello () {
console.log('hello,deejay');
}
上述代碼在解析時(shí)其實(shí)為
var a;
function sayHello() {console.log('hello,deejay');} //解析時(shí),var聲明的變量和function聲明的函數(shù) 會前置
console.log(a); //undefined
a = 3;
console.log(a); //3
sayHello();
另外,如果有變量名和函數(shù)聲明的函數(shù)名相同的情況,后面的值會覆蓋前面的值,產(chǎn)生報(bào)錯(cuò)。
對于函數(shù)表達(dá)式定義的函數(shù),前置的方式跟var一個(gè)變量沒什么區(qū)別。
console.log(sayHello);//undefined
var sayHello = function () {
console.log('hello,deejay');
}
sayHello(); // 特別要注意,對于函數(shù)表達(dá)式定義的函數(shù),只能先定義,然后再調(diào)用,不然會報(bào)錯(cuò)。
上述代碼等價(jià)于
var sayHello;
console.log(sayHello);//undefined
sayHello = function () {
console.log('hello,deejay');
}
sayHello(); // 特別要注意,對于函數(shù)表達(dá)式定義的函數(shù),只能先定義,然后再調(diào)用,不然會報(bào)錯(cuò)。
- 函數(shù)內(nèi)部的聲明前置
在一個(gè)作用域內(nèi),var定義的變量和function聲明的函數(shù)會前置,那么在函數(shù)內(nèi)部的作用域中,前置規(guī)則也是一樣的。
function doSomething () {
console.log(a); // undefined
var a = 3;
console.log(a); //3
//上面代碼其實(shí)等價(jià)于下面代碼:
// var a;
// console.log(a); // undefined
// a = 3;
// console.log(a); //3
}
doSomething(); //調(diào)用函數(shù),進(jìn)入函數(shù)作用域
- 變量和函數(shù)命名沖突時(shí)
當(dāng)命名發(fā)生沖突時(shí),先進(jìn)行前置,再進(jìn)行覆蓋
var fn = 3;
function fn () {}
console.log(fn); //3
上述代碼等價(jià)于
var fn;
function fn() {} //此時(shí)fn為函數(shù)
// console.log(typeof fn); //function
fn = 3;
// console.log(typeof fn); //number
console.log(fn); //3
同理:
function fn() {}
var fn = 3;
console.log(fn); //3
等價(jià)于
function fn() {} // fn為一個(gè)全局函數(shù)
var fn; // 前面fn函數(shù)已經(jīng)存在,此時(shí)兵沒有給fn賦值,所以fn仍然是一個(gè)函數(shù)
// console.log(typeof fn); //function
fn = 3; // 此時(shí)給fn賦值了之后,fn變?yōu)閿?shù)值
// console.log(typeof fn); //number
console.log(fn); // 3
- 函數(shù)名和參數(shù)名重名時(shí),即如下情況:
function fn (fn) {
console.log(fn);
var fn = 3;
console.log(fn);
}
fn(5); //5 3
此時(shí)運(yùn)行的過程等價(jià)于:
function fn (fn) {
var fn = 5;//這條語句是JS自動(dòng)隱藏添加的,當(dāng)傳入?yún)?shù)時(shí),給fn賦值
var fn; // 函數(shù)內(nèi)部作用域變量提升
console.log(fn); // 此時(shí)輸出的為傳入的已經(jīng)賦值的參數(shù)fn,而不是undefined
fn = 3;
console.log(fn); // 輸出的是當(dāng)前作用域內(nèi)的局部變量fn
}
3.arguments 是什么
在函數(shù)內(nèi)部,可以使用arguments對象獲取到該函數(shù)的所有傳入?yún)?shù),是一個(gè)類數(shù)組對象。
var getInfo = function () {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
getInfo('deejay',21,'male');
4. 函數(shù)的"重載"怎樣實(shí)現(xiàn)
JS沒有重載! 同名的函數(shù)會覆蓋,但是可以在函數(shù)體內(nèi)針對不同的參數(shù)調(diào)用執(zhí)行相應(yīng)的邏輯
可以模擬重載,舉例說明:
var getInfo = function (name,age,sex) {
if (name) {
console.log(name);
}
if (age) {
console.log(age);
}
if (sex) {
console.log(sex)
}
}
getInfo('deejay',21); //deejay 21
getInfo('deejay',21,'male'); //deejay 21 male
5.立即執(zhí)行函數(shù)表達(dá)式是什么?有什么作用
(function () {
console.log('hello,deejay');
})();
創(chuàng)建一個(gè)匿名函數(shù)并且立即調(diào)用它,一般用于隔離作用域(因?yàn)槠鋬?nèi)部的函數(shù)作用域不受外部作用域的影響)
6.求n!,用遞歸來實(shí)現(xiàn)
遞歸
- 自己調(diào)用自己
- 設(shè)定終止條件
- 優(yōu)點(diǎn):算法簡單
- 缺點(diǎn):效率低
求n!的遞歸實(shí)現(xiàn):
function fn (n) {
if (n <= 0){
console.log('n為正整數(shù)');
return;
}
else if (n === 1) {
return 1;
}
else if (n >=1 ){
return n * fn(n - 1);
}
}
var result = fn(5);
console.log(result);
7. 分析輸出結(jié)果
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('deejay', 21, '男');
getInfo('dee', 3);
getInfo('男');
輸出結(jié)果為:
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('deejay', 21, '男');
// 輸出結(jié)果為:
// name: deejay
// age: 2
// sex: 男
// ['deejay',21,'男']
// name valley
getInfo('deejay', 3);
// 輸出結(jié)果為
// name: deejay
// age: 3
// sex: undefined
// ['deejay',3]
// name valley
getInfo('男');
// 輸出結(jié)果為
// name: 男
// age: undefined
// sex: undefined
// ['男']
// name valley
8. 寫一個(gè)函數(shù),返回參數(shù)的平方和?
function sumOfSquares(){
}
var result2 = sumOfSquares(1,3)
console.log(result) //29
console.log(result2) //10
平方和代碼如下:
function sumOfSquares(){
var sum = 0;
for (var i = 1; i <= arguments.length; i ++) {
sum += Math.pow(arguments[i-1],2);
}
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);
輸出解釋如下:
console.log(a);
var a = 1;
console.log(b);
// 等價(jià)于
var a;
console.log(a); //undefined 預(yù)解析,聲明了a,但是沒賦值,為undefined
a = 1;
console.log(b); //Uncaught ReferenceError: b is not defined 沒有聲明b,報(bào)錯(cuò)
10.如下代碼的輸出?為什么
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
解釋如下:
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
// 表達(dá)式定義的函數(shù),在進(jìn)行前置的時(shí)候,跟用var聲明的變量規(guī)則一樣
// 等價(jià)于:
// function sayName(name){
// console.log('hello ', name);
// }
// var sayAge;
// sayName('world');//hello, world
// sayAge(10); //Uncaught TypeError: sayAge is not a function 此時(shí)sayAge()只是被聲明,并不是一個(gè)函數(shù), 報(bào)錯(cuò)
// sayAge = function (age) {
// console.log(age);
// }
11. 如下代碼輸出什么? 寫出作用域鏈查找過程偽代碼
var x = 10
bar()
function foo() {
console.log(x) //10
}
function bar(){
var x = 30
foo()
}
偽代碼如下:
GlobalContext = {
AO: {
x: 10,
foo: function () {},
bar: function () {},
},
}
foo.[[scope]] = GlobalContext.AO;
bar.[[scope]] = GlobalContext.AO;
fooContext = {
AO: {},
scope: GlobalContext.AO
}
barContext = {
AO:{
x:30,
},
scope: GlobalContext.AO
}
可以看出輸出的是GlobalContext.AO中的x的值,為10。
12.如下代碼輸出什么? 寫出作用域鏈查找過程偽代碼
var x = 10;
bar()
function bar(){
var x = 30;
function foo(){
console.log(x)
}
foo();
}
偽代碼如下:
GlobalContext = {
AO: {
x:10,
bar: function() {}
},
}
bar.[[scope]] = GlobalContext.AO
barContext = {
AO : {
x: 30,
foo: function () {}
},
scope: GlobalContext.AO
}
fooContext = {
AO: {},
scope: barContext.AO
}
很明顯輸出的是barContext.AO中的x值,為30
13.以下代碼輸出什么? 寫出作用域鏈的查找過程偽代碼
var x = 10;
bar()
function bar(){
var x = 30;
(function (){
console.log(x)
})()
}
偽代碼如下:
GlobalContext = {
AO: {
x: 10,
bar: function() {}
}
}
bar.[[scope]] = GlobalContext.AO
barContext = {
AO: {
x: 30,
匿名函數(shù): function () {}
},
scope: GlobalContext.AO
}
匿名函數(shù)Context = {
AO: {},
scope: barContext.AO
}
顯然輸出的是barContext.AO中的x值,為30
14.以下代碼輸出什么? 寫出作用域鏈查找過程偽代碼
var a = 1;
function fn(){
console.log(a)
var a = 5
console.log(a)
a++
var a
fn3()
fn2()
console.log(a)
function fn2(){
console.log(a)
a = 20
}
}
function fn3(){
console.log(a)
a = 200
}
fn()
console.log(a)
偽代碼為:
開始執(zhí)行程序時(shí)的狀態(tài)值為:
GlobalContext = {
AO: {
a:1,
fn: function () {},
fn3: function () {}
},
}
fnContext = {
AO: {
a:undefined,//解析時(shí)的值是undefined,
fn2: function () {}
},
scope: GlobalContext.AO // fn的上一級作用域?yàn)間lobal
}
fn3Context = {
AO: {
沒有任何活動(dòng)對象,注意:a = 200,沒有用var聲明,不是當(dāng)前作用域即fn3的作用域中的活動(dòng)對象
},
scope: GlobalContext.AO // fn3的上一級為global
}
fn2Context = {
AO: {
沒有任何活動(dòng)對象,注意:a = 20,沒有用var聲明,不是當(dāng)前作用域即fn2的作用域中的活動(dòng)對象
},
scope: fnContext.AO // fn2的上一級作用域?yàn)閒n
}
最終的輸出結(jié)果為:
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; //改變了fn中的a(6 ----> 20)
}
}
function fn3 () {
console.log(a); //1
a = 200; //改變了全局中的a (1 ----> 200)
}
fn();
console.log(a); //200
按照運(yùn)行順序依次輸出為: undefined 5 1 6 20 200