前面系列即爲前端面試系列(Front-end interview series), 主要內容是一些前端面試中常常被問到的題.html
系列問答中沒有繁瑣的講解過程, 力求保證面試者給予面試官一個簡潔、具備重點的答案, 因此適合於有必定知識基礎的前端童鞋👨🎓. 固然, 在每題的最後我也會貼上關於這一章節比較好文章, 以供你們更好的理解所提到的知識點.前端
請認準github地址: LinDaiDai-FInode
this
會綁定到
undefined
)
obj.foo()
的調用方式,
foo
內的
this
指向
obj
)
call()
或者
apply()
方法直接指定
this
的綁定對象, 如
foo.call(obj)
)
this
的指向由外層做用域決定的)
注⚠️git
隱式丟失github
」
被隱式綁定的函數在特定的狀況下會丟失綁定對象, 應用默認綁定, 把this
綁定到全局對象或者undefined
上:web
function foo () {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
var bar = obj.foo; // 使用另外一個變量賦值
var a = 2;
bar(); // 2
複製代碼
// 參數傳遞形成的隱式綁定丟失
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo // 即便換成 () => foo() 也沒用
}
function doFoo(fn) {
fn();
}
var a = 2;
doFoo(obj.foo) // 2
複製代碼
解決顯示綁定中丟失綁定問題面試
」
// 硬綁定
function foo(params) {
console.log(this.a, params);
return this.a + params;
}
var bar = function() {
return foo.apply(obj, arguments);
}
var obj = {
a: 1
}
var a = 2;
console.log(bar(3)) // 1, 3; return 4
複製代碼
// 1.簡單的輔助綁定函數
function bind (obj, fn) {
return function () {
return fn.apply(obj, arguments);
}
}
// 2. ES5內置了 Function.prototype.bind
var bar = foo.bind(obj);
複製代碼
JS
中一些內置函數(數組的
forEach、map、filter
)提供的可選參數, 能夠指定綁定
this
, 其做用和
bind
同樣:
// 內置函數提供的可選參數, 指定綁定this
function foo(el) {
console.log(el, this.a)
}
var obj = {
a: 'obj a'
};
var a = 'global a';
var arr = [1, 2, 3];
arr.forEach(foo, obj) // 第二個參數爲函數的this指向
// 1 'obj a', 2 'obj a', 3 'obj a'
複製代碼
詳細指南: 《木易楊前端進階-JavaScript深刻之史上最全--5種this綁定全面解析》數組
[[prototype]]
鏈接, 將新對象的原型指向構造函數,這樣新對象就能夠訪問到構造函數原型中的屬性
this
的指向爲新建的對象,這樣新對象就能夠訪問到構造函數中的屬性
詳細指南: 《木易楊前端進階-JavaScript深刻之史上最全--5種this綁定全面解析》瀏覽器
語法:緩存
func.apply(thisArg, [argsArray])
func.call(thisArg, arg1, arg2, ...)
複製代碼
Array.prototype.push.apply(arr1, arr2)
)
Math.max.apply(null, arr)
)
Object.prototype.toString.call(obj)
)
Array.prototype.slice.call(domNodes)
或者
[].slice.call(domNodes)
)
SuperType.call(this)
)
Object.prototype.hasOwnProperty.call(obj)
來檢測
Object.create(null)
這種對象
注⚠️:
關於第6點:
全部普通對象均可以經過 Object.prototype
的委託來訪問 hasOwnProperty(...)
,可是對於一些特殊對象( Object.create(null)
建立)沒有鏈接到 Object.prototype
,這種狀況必須使用 Object.prototype.hasOwnProperty.call(obj, "a")
,顯示綁定到 obj
上。又是一個 call
的用法。
例如🌰:
var obj = Object.create(null);
obj.name = 'objName';
console.log(Object.prototype.hasOwnProperty.call(obj5, 'name')); // true
複製代碼
詳細指南: 《木易楊前端進階-深度解析 call 和 apply 原理、使用場景及實現》
問題緣由:
Array.prototype.push.apply(arr1, arr2);
// or
Array.prototype.push.call(arr1, ...arr2);
複製代碼
因此爲了解決第二個數組長度太大的問題, 咱們能夠將參數數組切塊後循環傳入目標數組中:
function connectArray (arr1, arr2) {
const QUANTUM = 32768;
for (let i = 0, len = arr2.length; i < len; i += QUANTUM) {
Array.prototype.push.apply(
arr1,
arr2.slice(i, Math.min(i + QUANTUM, len))
)
}
return arr1;
}
複製代碼
測試:
var arr1 = [-3, -2, -1];
var arr2 = [];
for (let i = 0; i < 100000; i++) {
arr2.push(i);
}
connectArray(arr1, arr2);
// arr1.length // 100003
複製代碼
詳細指南: 《木易楊前端進階-深度解析 call 和 apply 原理、使用場景及實現》
在Object.prototype.toString()
沒有被修改的狀況下, 咱們能夠用它結合call
來獲取數據類型:
[[Class]]
是一個內部屬性,值爲一個類型字符串,能夠用來判斷值的類型。
// 手寫一個獲取數據類型的函數
function getClass(obj) {
let typeString = Object.prototype.toString.call(obj); // "[object Array]"
return typeString.slice(8, -1);
}
console.log(getClass(new Date)) // Date
console.log(getClass(new Map)) // Map
console.log(getClass(new Set)) // Set
console.log(getClass(new String)) // String
console.log(getClass(new Number)) // Number
console.log(getClass(NaN)) // Number
console.log(getClass(null)) // Null
console.log(getClass(undefined)) // Undefined
console.log(getClass(Symbol(42))) // Symbol
console.log(getClass({})) // Object
console.log(getClass([])) // Array
console.log(getClass(function() {})) // Function
console.log(getClass(document.getElementsByTagName('p'))) // HTMLCollection
console.log(getClass(arguments)) // Arguments
複製代碼
Array.prototype.slice.call(arguments);
// 等同於 [].slice.call(arguments);
ES6:
let arr = Array.from(arguments);
let arr = [...arguments];
複製代碼
Array.from()
能夠將兩類對象轉爲真正的數組:類數組對象和可遍歷(iterable)對象(包括ES6新增的數據結構 Set 和 Map), 好比:
var map1 = new Map();
map1.set("key1", "value1")
map1.set("key2", "value2")
var mapArr = Array.from(map1)
console.log(map1) // Map
console.log(mapArr) // [["key1", "value1"], ["key2", "value2"]] 二維數組
複製代碼
擴展一: 爲何經過 Array.prototype.slice.call()
就能夠把類數組對象轉換成數組 🤔️?
答: 由於slice
將類數組對象經過下標操做放入了新的數組中
擴展二: 經過 Array.prototype.slice.call()
就足夠了嗎?存在什麼問題 🤔️?
答: 在低版本的IE下不支持Array.prototype.slice.call(args)
這種寫法, 由於低版本IE(IE < 9)下的DOM
對象是以 com
對象的形式實現的,js對象與 com
對象不能進行轉換。
兼容的寫法爲:
function toArray (nodes) {
try {
return Array.prototype.slice.call(nodes);
} catch (err) {
var arr = [],
len = nodes.length;
for (var i = 0; i < len; i++) {
arr.push(nodes[i]);
}
return arr;
}
}
複製代碼
擴展三: 爲何要有類數組對象呢?或者說類數組對象是爲何解決什麼問題纔出現的 🤔️?
一句話就是能夠更快的操做複雜數據, 好比音頻視頻編輯, 訪問webSockets的原始數據等.
語法:
func.bind(thisArg, arg1, arg2, ...)
複製代碼
咱們知道, bind()
方法的做用是會建立一個新函數, 在這個新函數被調用時, 函數內的this
指向bind()
的第一個參數, 而其他的參數將做爲新函數的參數被它使用.
因此它與apply/call
最大的區別是bind
會返回一個綁定上下文的函數, 然後二者會直接執行這個函數.
在使用場景上:
this
的指向, 好比解決隱式綁定的函數丟失
this
的狀況
Function.prototype.call.bind(Object.prototype.toString)
來獲取數據類型(前提是
Object.prototype.toString
方法沒有被覆蓋
bind
是會返回一個新函數的, 因此咱們還能夠用它來實現柯里化,
bind
自己也是閉包的一種使用場景.
詳細指南: 《木易楊前端進階-深度解析bind原理、使用場景及模擬實現》
/** * 非嚴格模式 */
var name = 'window'
var person1 = {
name: 'person1',
show1: function () {
console.log(this.name)
},
show2: () => console.log(this.name),
show3: function () {
return function () {
console.log(this.name)
}
},
show4: function () {
return () => console.log(this.name)
}
}
var person2 = { name: 'person2' }
person1.show1()
person1.show1.call(person2)
person1.show2()
person1.show2.call(person2)
person1.show3()()
person1.show3().call(person2)
person1.show3.call(person2)()
person1.show4()()
person1.show4().call(person2)
person1.show4.call(person2)()
複製代碼
空
白
格
答案:
person1.show1() // person1 隱式綁定, this指向調用者
person1.show1.call(person2) // person2 顯示綁定, this指向person2
person1.show2() // window,箭頭函數綁定,this指向外層做用域,即全局做用域
person1.show2.call(person2) // window, 使用call硬綁定person2也沒用,this指向外層做用域,即全局做用域
person1.show3()() // window, 默認綁定, 此函數爲高階函數, 調用者是window
// 能夠理解爲隱性丟失,使用另外一個變量來給函數取別名: var bar = person1.show3();
person1.show3().call(person2)// person2, 顯式綁定, 將 `var bar = person1.show3()` 這個函數的this 指向 person2
person1.show3.call(person2)() // window, 默認綁定, 雖然將第一層函數內的this指向了person2, 可是內層函數 `var bar = person1.show3()` 的調用者仍是window
person1.show4()() // person1, 第一層函數的this是person1, 內層爲箭頭函數, 指向外層做用域person1
person1.show4().call(person2) // person1, 第一層函數的this是person1, 內層爲箭頭函數,使用call硬綁定person2也沒用,this仍是指向外層做用域person1
person1.show4.call(person2)() // person2, 改變了第一層函數的this指向, 將其指向爲person2, 而內層爲箭頭函數, 指向外層做用域person2
複製代碼
換一種方式: 使用構造函數來建立對象, 並執行4個相同的show
方法:
提示: 使用new
操做符建立的對象和直接var
產生的對象的區別在於:
使用new操做符會產生新的構造函數做用域, 這樣箭頭函數內的this指向的就是這個函數做用域, 而非全局
var name = 'window'
function Person (name) {
this.name = name;
this.show1 = function () {
console.log(this.name)
}
this.show2 = () => console.log(this.name)
this.show3 = function () {
return function () {
console.log(this.name)
}
}
this.show4 = function () {
return () => console.log(this.name)
}
}
var personA = new Person('personA')
var personB = new Person('personB')
personA.show1()
personA.show1.call(personB)
personA.show2()
personA.show2.call(personB)
personA.show3()()
personA.show3().call(personB)
personA.show3.call(personB)()
personA.show4()()
personA.show4().call(personB)
personA.show4.call(personB)()
複製代碼
空
白
格
答案:
personA.show1() // personA,隱式綁定,調用者是 personA
personA.show1.call(personB) // personB,顯式綁定,調用者是 personB
personA.show2() // personA, 與第一題的區別, 此時this指向的是外層做用域 personA函數的做用域
personA.show2.call(personB) // personA, 箭頭函數使用call硬綁定也沒用
personA.show3()() // window, 默認綁定, 調用者是window, 同第一題同樣
personA.show3().call(personB) // personB, 顯示綁定
personA.show3.call(personB)() // window, 默認綁定,調用者是window, 同第一題同樣
personA.show4()() // personA, 箭頭函數綁定,this指向外層做用域,即personA函數做用域
personA.show4().call(personB) // personA, 箭頭函數綁定,call並無改變外層做用域,
personA.show4.call(personB)() // personB, 將第一層函數的this指向改爲了personB, 此時做用域指向personB, 內存函數爲箭頭函數, this指向外層做用域,即personB函數做用域
複製代碼
function create () {
var obj = new Object(),
Con = [].shift.call(arguments);
obj.__proto__ = Con.prototype;
var ret = Con.apply(obj, arguments);
return ret instanceof Object ? ret : obj;
}
複製代碼
空
白
格
過程分析:
function create () {
// 1. 建立一個新的對象
var obj = new Object(),
// 2. 取出第一個參數, 就是咱們要傳入的構造函數; 同時arguments會被去除第一個參數
Con = [].shift.call(arguments);
// 3. 將 obj的原型指向構造函數,這樣obj就能夠訪問到構造函數原型中的屬性
obj.__proto__ = Con.prototype;
// 4. 使用apply,改變構造函數this 的指向到新建的對象,這樣 obj就能夠訪問到構造函數中的屬性
var ret = Con.apply(obj, arguments);
// 5. 優先返回構造函數返回的對象
return ret instanceof Object ? ret : obj;
}
複製代碼
詳細指南: 《木易楊前端進階-深度解析 new 原理及模擬實現》
ES3寫法:
// 建立一個獨一無二的 fn 函數名
function fnFactory(context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.call2 = function (context) {
context = context ? Object(context) : window;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var fn = fnFactory(context)
context[fn] = this;
var result = eval('context[fn](' + args + ')');
delete context[fn];
return result;
}
複製代碼
ES6寫法:
Function.prototype.call3 = function (context) {
context = context ? Object(context) : window;
var fn = Symbol();
context[fn] = this;
let args = [...arguments].slice(1);
let result = context[fn](...args);
delete context[fn];
return result;
}
複製代碼
空
白
格
過程分析:
// 建立一個獨一無二的 fn 函數名
function fnFactory(context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.call2 = function(context) {
// 1. 如果傳入的context是null或者undefined時指向window;
// 2. 如果傳入的是原始數據類型, 原生的call會調用 Object() 轉換
context = context ? Object(context) : window;
// 3. 建立一個獨一無二的fn函數的命名
var fn = fnFactory(context);
// 4. 這裏的this就是指調用call的那個函數
// 5. 將調用的這個函數賦值到context中, 這樣以後執行context.fn的時候, fn裏的this就是指向context了
context[fn] = this;
// 6. 定義一個數組用於放arguments的每一項的字符串: ['agruments[1]', 'arguments[2]']
var args = [];
// 7. 要從第1項開始, 第0項是context
for (var i = 1, l = arguments.length; i < l; i++) {
args.push('arguments[' + i + ']')
}
// 8. 使用eval()來執行fn並將args一個個傳遞進去
var result = eval('context[fn](' + args + ')');
// 9. 給context額外附件了一個屬性fn, 因此用完以後須要刪除
delete context[fn];
// 10. 函數fn可能會有返回值, 須要將其返回
return result;
}
複製代碼
測試代碼:
var obj = {
name: 'objName'
}
function consoleInfo(sex, weight) {
console.log(this.name, sex, weight)
}
var name = 'globalName';
consoleInfo.call2(obj, 'man', 100); // 'objName' 'man' 100
consoleInfo.call3(obj, 'woman', 120); // 'objName' 'woman' 120
複製代碼
ES3:
// 建立一個獨一無二的 fn 函數名
function fnFactory (context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = 'fn' + Math.random();
}
return unique_fn;
}
Function.prototype.apply2 = function (context, arr) {
context = context ? Object(context) : window;
var fn = fnFactory(context);
context[fn] = this;
var result;
if (!arr) {
result = context[fn]();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context[fn](' + args + ')');
}
delete context[fn];
return result;
}
複製代碼
ES6:
Function.prototype.apply3 = function (context, arr) {
context = context ? Object(context) : window;
let fn = Symbol();
context[fn] = this;
let result = arr ? context[fn](...arr) : context[fn]();
delete context[fn];
return result;
}
複製代碼
空
白
格
過程分析:
// 建立一個獨一無二的 fn 函數名
function fnFactory (context) {
var unique_fn = 'fn';
while (context.hasOwnProperty(unique_fn)) {
unique_fn = 'fn' + Math.random();
}
return unique_fn;
}
Function.prototype.apply2 = function (context, arr) {
// 1. 如果傳入的context是null或者undefined時指向window;
// 2. 如果傳入的是原始數據類型, 原生的call會調用 Object() 轉換
context = context ? Object(context) : window;
// 3. 建立一個獨一無二的fn函數的命名
var fn = fnFactory(context);
// 4. 這裏的this就是指調用call的那個函數
// 5. 將調用的這個函數賦值到context中, 這樣以後執行context.fn的時候, fn裏的this就是指向context了
context[fn] = this;
var result;
// 6. 判斷有沒有第二個參數
if (!arr) {
result = context[fn]();
} else {
// 7. 有的話則用args放每一項的字符串: ['arr[0]', 'arr[1]']
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
// 8. 使用eval()來執行fn並將args一個個傳遞進去
result = eval('context[fn](' + args + ')');
}
// 9. 給context額外附件了一個屬性fn, 因此用完以後須要刪除
delete context[fn];
// 10. 函數fn可能會有返回值, 須要將其返回
return result;
}
複製代碼
提示:
this
表示的就是調用的函數
this
的指向
new
操做法建立對象, 且提供的
this
會被忽略
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var innerArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context,
args.concat(innerArgs)
)
}
var fNOP = function () {};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
複製代碼
空
白
格
Function.prototype.bind2 = function(context) {
// 1. 判斷調用bind的是否是一個函數
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable")
}
// 2. 外層的this指向調用者(也就是調用的函數)
var self = this;
// 3. 收集調用bind時的其它參數
var args = Array.prototype.slice.call(arguments, 1);
// 4. 建立一個返回的函數
var fBound = function() {
// 6. 收集調用新的函數時傳入的其它參數
var innerArgs = Array.prototype.slice.call(arguments);
// 7. 使用apply改變調用函數時this的指向
// 做爲構造函數調用時this表示的是新產生的對象, 不做爲構造函數用的時候傳遞context
return self.apply(
this instanceof fNOP ? this : context,
args.concat(innerArgs)
)
}
// 5. 建立一個空的函數, 且將原型指向調用者的原型(爲了能用調用者原型中的屬性)
// 下面三步的做用有點相似於 fBoun.prototype = this.prototype 但有區別
var fNOP = function() {};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
// 8. 返回最後的結果
return fBound;
}
複製代碼
喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號👇👇👇.
我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉
你的鼓勵就是我持續創做的動力 😊.
相關推薦: