JS引用類型 --- 函數(含this指向面試題)
一、函數基礎
1. 函數:可重用的代碼塊
2. 函數可以作為參數、返回值使用
3. 函數實際上是 Function
的實例,其數據類型是Object
4. 但typeof Function
值為 function
; Function
、Object
、Array
等都是構造函數
5. 函數沒有重載,函數名重復後會被覆蓋
重載:其他語言,可以為一個函數編寫兩個定義,只要這兩個定義的簽名(接受參數的類型和數量)不同即可
深入理解函數 為什麽沒有重載:函數是引用類型的,函數名可理解為函數的指針,函數名重名,即為函數重新定義指針的指向。
6. 模擬實現 JS函數的重載
- 根據arguments對象的length值進行判斷,執行不同的代碼 參考
<script> function overLoading() { // 根據arguments.length,對不同的值進行不同的操作 switch (arguments.length) { case 0: /*操作1的代碼寫在這裏*/ break; case 1: /*操作2的代碼寫在這裏*/ break; case 2: /*操作3的代碼寫在這裏*/ //後面還有很多的case...... } } </script>
二、函數 創建 的3種方式
1. 函數聲明 方式 function
- 預解析是將整個函數體提前提前
<script>
fn(); // 可以訪問到
function fn() {
}
console.log(fn); //輸出整個函數體, 即:function fn() {}
</script>
2. 函數表達式 方式 var fn =
- 預解析是將
var fn;
提前,並沒有將函數體提前
<script>
fn(); // 不能訪問到,報錯
var fn = function () {
}
console.log(fn); //輸出整個函數體, 即:function fn() {}
</script>
3. 構造函數 方式 new Function
new Function(‘新函數參數‘,‘新函數參數‘,....,‘函數體‘)
- 註意:
- 參數都是字符串;
- 最後一個字符串參數是 新函數體;前面所有參數都是 新函數的 參數;
<script>
var fn = new Function(‘a‘, ‘b‘, ‘c‘, ‘return a+b+c‘);
console.log(fn(1, 2, 3));
</script>
- 作用:將字符串,轉為可執行的代碼
- 模板引擎原理 new Function()
- 能夠將json 對象形式的字符串 轉為 json 對象,代碼如下:
<script>
var str = ‘{name: "zhangxin", age: 18}‘;
var aa = new Function(‘return‘ + str);
console.log(aa());
</script>
4. 函數在哪個作用域內創建的,就會在哪個作用域內執行;與函數調用的環境無關
5. 函數一創建,就有了 prototype
屬性,指向原型對象(Math函數除外)
6. 匿名函數 的表現形式
function () {};
var f = function () {};
三、函數的 參數
1. 形參
形參: 函數定義的參數
調用函數時,實參個數 不等於 形參個數時,不會報錯;因為,在函數體內有
arguments
來存儲這些實參
2. 實參
- 實參:由
arguments
統一管理,arguments
是一個偽數組,只有.length
屬性
3. 參數可看做 參數函數內部的局部變量;且參數的 聲明和賦值 被提升到函數最頂端
傳實參 就是 給局部變量初始化
不傳實參 則參數值為
undefined
4. 關於 函數形參 與 函數內變量 重名問題分析?
參數 可以看做是函數內部的局部變量
如果 形參 與 函數內 的變量名沖突,則後者會覆蓋前者
如果 形參 與 函數內 的函數名(函數聲明方式創建的函數)沖突,則形參變為函數
<script>
function fn(m) {
console.log(m);
m = 2;
console.log(m);
}
fn(10);
</script>
// 10 2
<script>
function fn(m) {
console.log(m);
function m() {};
console.log(m);
}
fn(10);
</script>
// 函數體 函數體
<script>
function fn(m) {
console.log(m);
m = function () {};
console.log(m);
}
fn(10);
</script>
// 10 函數體
四、函數的 返回值
函數的返回值:可有可無,不會報錯
沒有返回值、沒有明確的返回值,輸出函數的調用,結果為 undefined
<script>
function fn1() {
console.log("aaa");
return;
}
console.log(fn1()); //aaa undefined
</script>
- 函數可以作為返回值:被返回
五、函數的 直接屬性和方法
1. 屬性:length
,獲取形參的個數
2. 屬性:prototype
,指向原型對象,保存函數的所有實例方法
3. 屬性:name
, 獲取當前的函數名(IE不支持)
4. 屬性:caller
,(被廢棄)可以獲得當前函數在哪個函數內部被調用,全局調用,結果為null
5. 方法借用:apply()
(非繼承來的方法)
6. 方法借用:call()
(非繼承來的方法)
方法借用 並自動調用; 改變
this
指向apply()
、call()
借用方法後立即被自動調用, 返回值是:借用方法的返回值- 借用方法:
apply()
、call()
第1個參數:
this
指向的對象,可不傳;(不傳、傳null、傳undefined時,this都指向window);第2個參數:借用方法的參數
返回值是:借用方法的返回值
apply()
、call()
區別 在於方法內的第二個參數方法.apply()
: 第2個參數:數組,數組內每一項是借用方法的參數方法.call()
:第2個、第3個等參數:是單獨的項,是借用方法的參數
apply()
借用 Math.max 求數組中的最大值
Math.max.apply(null, [14, 3, 77])
<script>
var arr = [1, 2, 3, 4];
var result1 = arr.toString(); // 1,2,3,4 使用了數組實例自己的toString()方法
var result2 = Object.prototype.toString.call(arr); // 借用了對象的toString()方法
var result3 = Object.prototype.toString.apply([arr]);
console.log(result1); // 1,2,3,4
console.log(result2); // [object Array]
console.log(result3); // [object Array]
</script>
<script>
function fn () {
console.log(this);
}
fn.call(); // window
fn.call(null); // window
fn.call(undefined); // window
</script>
5. 方法借用 bind()
(ES5定義的)
方法借用 需手動調用; 改變this指向
返回值:借用的方法(需要手動調用,這就是和
apply
call
的區別)用法:
方法.bind(this指向的對象)
; (不傳、傳null、傳undefined時, this指向window)
<script>
var bar = function(a, b) {
console.log(this.x);
console.log(a);
console.log(b);
}
var foo = {
x: 3
}
bar.bind(foo); // 無輸出,因為 借用了方法,還沒被調用
bar.bind(foo)(1, 2); // 3 1 2
</script>
<script>
var length = 20;
var fn = function () {
console.log(this.length);
};
var obj = {
length: 10
};
fn.bind()(); // 20
fn.bind(obj)(); // 10
</script>
<script>
const length = 20;
var fn = function () {
console.log(this.length);
};
var obj = {
length: 10
};
fn.bind()(); // 0, const 聲明的變量在塊級作用域中,不在window中,window中length屬性值為0
</script>
六、函數的 內部屬性(成員)
1. arguments
對象;保存實參;偽數組
每一個函數體內,都有一個
arguments
;只能在函數體內訪問偽數組,只有
.length
屬性;轉為真數組:[].slice.apply(arguments)
是一個對象,屬性:
arguments.length
; 取值:arguments[0]
2. arguments.callee
指向當前函數:解耦實現函數的遞歸
arguments.callee
指向一個正在執行的函數的指針
<script>
function aaa() {
console.log(arguments.callee); // 整個函數體
return arguments.callee(); // 實現函數的遞歸(解耦)
}
aaa();
</script>
3. this
對象:
this具有動態性,【誰調用函數,函數內部的this指向誰】 ; 【執行函數時,要回到創建函數的環境下執行】
(1) 函數調用模式: this
指向 window
特殊:setTimeout / setInterval 等回調函數中, this 的指向:window,因為回調函數是window調用的
(2) 方法調用模式: this
指向 調用方法的對象
特殊:DOM 綁定事件,事件回調函數中 this 指向 DOM
涉及arr[0] = 函數,實際上函數作為 arr對象中的方法被調用
實際上:this指向數組對象,解釋:【數組是對象,arr[0] ===> 數組對象.方法】
(3) 構造函數調用模式: this
指向 新創建的對象
(4) 方法借用模式: this
指向,參數對象 / 不傳參或者穿 null undefined 的話this
指向 window
this面試題
- 第1題,輸出什麽
<script>
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 3,
method: function (fn) {
(false || arguments[0])();
}
};
obj.method(fn, 123, 14, 12, 4);
</script>
- 第2題,輸出什麽
<script>
var arr = [];
arr[0] = function () {
console.log(this.length);
}
arr[1] = 123;
arr[2] = 22;
arr[0]();
</script>
- 第3題,輸出什麽
<script>
var age = 38;
var obj = {
age: 18,
getAge: function () {
function fn() {
console.log(this.age);
}
fn();
}
};
obj.getAge();
</script>
- 第4題,輸出什麽
<script>
var obj ={
say: function () {
console.log(this);
}
};
obj.say();
(obj.say)();
(obj.say = obj.say)();
(false || obj.say)();
</script>
- 第5題,改變this指向,讓其指向 obj
<script>
function fn() {
console.log(this);
}
var obj = {};
// 方法1:
obj.fn = fn;
obj.fn();
// 方法2:
fn.apply(obj);
// 方法3:
fn.call(obj);
// 方法4:
fn.bind(obj)();
</script>
- 第6題,判斷this指向
<script>
var obj = {
fn: function () {
console.log(this);
}
};
setTimeout(obj.fn, 1000);
</script>
- 第7題,使用setTimeout調用函數,並使 this 指向 obj
<script>
var obj = {
fn: function () {
console.log(this);
}
};
// 方法1:
setTimeout(obj.fn.bind(obj), 1000);
setTimeout(obj.fn.apply(obj), 1000);
setTimeout(obj.fn.call(obj), 1000);
// 方法2:
setTimeout(function () {
obj.fn();
}, 1000);
</script>
- 第8題,輸出什麽 【重要】
<script>
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function (f) {
f();
arguments[0]();
arguments[0].call(this);
arguments[0].call();
}
};
var obj2 = {
method: (function () {
console.log(this);
})()
};
obj.method(fn);
</script>
- 第9題,輸出什麽 【重要】
<script>
var name = ‘windowName‘;
var obj = {
name: ‘objName‘,
getName: function() {
return this.name;
}
};
console.log(obj.getName());
console.log((obj.getName)());
console.log((obj.getName = obj.getName)()); // 先賦值,再執行賦值後的結果;因為賦值表達式的值是本身,所以this不能得到維持
</script>
- 第10題,修改obj中的代碼,使輸出結果為
windowName
<script>
var name = ‘windowName‘;
var obj = {
name: ‘objName‘,
getName: function() {
return this.name;
}
};
console.log(obj.getName());
</script>
- 第11題,輸出什麽
<script>
var name = ‘windowName‘;
var obj = {
name: ‘objName‘,
getName: function() {
return function() {
return this.name;
}
}
};
console.log(obj.getName()());
</script>
- 第12題:修改obj中代碼,是輸出結果為
objName
<script>
var name = ‘windowName‘;
var obj = {
name: ‘objName‘,
getName: function() {
return function() {
return this.name;
}
}
};
console.log(obj.getName()());
</script>
this面試題答案
第1題: 10
第2題: 3
第3題: 38
第4題: obj obj window window
第6題: window
第8題: window 10 1 5 10
第9題: objName objName windowName
第10題:
<script>
var name = ‘windowName‘;
var obj = {
name: ‘objName‘,
getName: function() {
return (function () {
return this.name
})()
}
};
console.log(obj.getName()); // objName
</script>
第11題:windowName
第12題:
<script>
var name = ‘windowName‘;
var obj = {
name: ‘objName‘,
getName: function() {
var that = this;
return function() {
return that.name;
}
}
}
console.log(obj.getName()());
</script>
七、JS中 將字符串轉為 可執行的代碼 eval()
、 Function()
eval( ) 將字符串轉為可執行的代碼、和Function相同功能,區別在哪?
Function 和 eval 的區別和聯系:
相同點:都能夠將字符串轉為js 代碼來執行
不同點:eval效率相對高、但全局汙染比較嚴重,Function 反之
權衡利弊
考慮效率使用eval
考慮安全性用Function
eval 會造成 全局汙染
<script>
// 將JSON字符串轉化為js對象
var jsonStr = ‘var obj = {"name": "小明", "age": 19}‘;
eval(jsonStr);
console.log(obj);
</script>
JS引用類型 --- 函數(含this指向面試題)