手寫題:實現柯里化
預先設定一些引數
柯里化是什麼:是指這樣一個函式,它接收函式 A,並且能返回一個新的函式,這個新的函式能夠處理函式 A 的剩餘引數
function createCurry(func, args) {
var argity = func.length;
var args = args || [];
return function () {
var _args = [].slice.apply(arguments);
args.push(..._args);
if (args.length < argity) {
return createCurry.call(this, func, args);
}
return func.apply(this, args);
}
}
程式碼輸出結果
function Dog() {
this.name = 'puppy'
}
Dog.prototype.bark = () => {
console.log('woof!woof!')
}
const dog = new Dog()
console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)
輸出結果:true
解析: 因為constructor是prototype上的屬性,所以dog.constructor實際上就是指向Dog.prototype.constructor;constructor屬性指向建構函式。instanceof而實際檢測的是型別是否在例項的原型鏈上。
constructor是prototype上的屬性,這一點很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地來說,constructor的限制比較嚴格,它只能嚴格對比物件的建構函式是不是指定的值;而instanceof比較鬆散,只要檢測的型別在原型鏈上,就會返回true。
寫程式碼:實現函式能夠深度克隆基本型別
淺克隆:
function shallowClone(obj) {
let cloneObj = {};
for (let i in obj) {
cloneObj[i] = obj[i];
}
return cloneObj;
}
深克隆:
- 考慮基礎型別
引用型別
- RegExp、Date、函式 不是 JSON 安全的
- 會丟失 constructor,所有的建構函式都指向 Object
- 破解迴圈引用
function deepCopy(obj) {
if (typeof obj === 'object') {
var result = obj.constructor === Array ? [] : {};
for (var i in obj) {
result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
} else {
var result = obj;
}
return result;
}
說一下原型鏈和原型鏈的繼承吧
- 所有普通的 [[Prototype]] 鏈最終都會指向內建的 Object.prototype,其包含了 JavaScript 中許多通用的功能
- 為什麼能建立 “類”,藉助一種特殊的屬性:所有的函式預設都會擁有一個名為 prototype 的共有且不可列舉的屬性,它會指向另外一個物件,這個物件通常被稱為函式的原型
function Person(name) {
this.name = name;
}
Person.prototype.constructor = Person
- 在發生 new 建構函式呼叫時,會將建立的新物件的 [[Prototype]] 連結到 Person.prototype 指向的物件,這個機制就被稱為原型鏈繼承
- 方法定義在原型上,屬性定義在建構函式上
- 首先要說一下 JS 原型和例項的關係:每個建構函式 (constructor)都有一個原型物件(prototype),這個原型物件包含一個指向此建構函式的指標屬性,透過 new 進行建構函式呼叫生成的例項,此例項包含一個指向原型物件的指標,也就是透過 [[Prototype]] 連結到了這個原型物件
- 然後說一下 JS 中屬性的查詢:當我們試圖引用例項物件的某個屬性時,是按照這樣的方式去查詢的,首先查詢例項物件上是否有這個屬性,如果沒有找到,就去構造這個例項物件的建構函式的 prototype 所指向的物件上去查詢,如果還找不到,就從這個 prototype 物件所指向的建構函式的 prototype 原型物件上去查詢
- 什麼是原型鏈:這樣逐級查詢形似一個鏈條,且透過 [[Prototype]] 屬性連結,所以被稱為原型鏈
- 什麼是原型鏈繼承,類比類的繼承:當有兩個建構函式 A 和 B,將一個建構函式 A 的原型物件的,透過其 [[Prototype]] 屬性連結到另外一個 B 建構函式的原型物件時,這個過程被稱之為原型繼承。
標準答案更正確的解釋
什麼是原型鏈?
當物件查詢一個屬性的時候,如果沒有在自身找到,那麼就會查詢自身的原型,如果原型還沒有找到,那麼會繼續查詢原型的原型,直到找到 Object.prototype 的原型時,此時原型為 null,查詢停止。
這種透過 透過原型連結的逐級向上的查詢鏈被稱為原型鏈
什麼是原型繼承?
一個物件可以使用另外一個物件的屬性或者方法,就稱之為繼承。具體是透過將這個物件的原型設定為另外一個物件,這樣根據原型鏈的規則,如果查詢一個物件屬性且在自身不存在時,就會查詢另外一個物件,相當於一個物件可以使用另外一個物件的屬性和方法了。
程式碼輸出問題
function Parent() {
this.a = 1;
this.b = [1, 2, this.a];
this.c = { demo: 5 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Child() {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
}
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();
輸出結果:
parent.show(); // 1 [1,2,1] 5
child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5
parent.show(); // 1 [1,2,1] 5
child1.show(); // 5 [1,2,1,11,12] 5
child2.show(); // 6 [1,2,1,11,12] 5
這道題目值得神帝,他涉及到的知識點很多,例如this的指向、原型、原型鏈、類的繼承、資料型別等。
解析:
- parent.show(),可以直接獲得所需的值,沒啥好說的;
- child1.show(),
Child
的建構函式原本是指向Child
的,題目顯式將Child
類的原型物件指向了Parent
類的一個例項,需要注意Child.prototype
指向的是Parent
的例項parent
,而不是指向Parent
這個類。 - child2.show(),這個也沒啥好說的;
- parent.show(),
parent
是一個Parent
類的例項,Child.prorotype
指向的是Parent
類的另一個例項,兩者在堆記憶體中互不影響,所以上述操作不影響parent
例項,所以輸出結果不變; - child1.show(),
child1
執行了change()
方法後,發生了怎樣的變化呢? - this.b.push(this.a),由於this的動態指向特性,this.b會指向
Child.prototype
上的b陣列,this.a會指向child1
的a屬性,所以Child.prototype.b
變成了[1,2,1,11]; - this.a = this.b.length,這條語句中
this.a
和this.b
的指向與上一句一致,故結果為child1.a
變為4; - this.c.demo = this.a++,由於
child1
自身屬性並沒有c這個屬性,所以此處的this.c
會指向Child.prototype.c
,this.a
值為4,為原始型別,故賦值操作時會直接賦值,Child.prototype.c.demo
的結果為4,而this.a
隨後自增為5(4 + 1 = 5)。 child2
執行了change()
方法, 而child2
和child1
均是Child
類的例項,所以他們的原型鏈指向同一個原型物件Child.prototype
,也就是同一個parent
例項,所以child2.change()
中所有影響到原型物件的語句都會影響child1
的最終輸出結果。- this.b.push(this.a),由於this的動態指向特性,this.b會指向
Child.prototype
上的b陣列,this.a會指向child2
的a屬性,所以Child.prototype.b
變成了[1,2,1,11,12]; - this.a = this.b.length,這條語句中
this.a
和this.b
的指向與上一句一致,故結果為child2.a
變為5; - this.c.demo = this.a++,由於
child2
自身屬性並沒有c這個屬性,所以此處的this.c
會指向Child.prototype.c
,故執行結果為Child.prototype.c.demo
的值變為child2.a
的值5,而child2.a
最終自增為6(5 + 1 = 6)。
程式碼輸出結果
function a(xx){
this.x = xx;
return this
};
var x = a(5);
var y = a(6);
console.log(x.x) // undefined
console.log(y.x) // 6
輸出結果: undefined 6
解析:
- 最關鍵的就是var x = a(5),函式a是在全域性作用域呼叫,所以函式內部的this指向window物件。所以 this.x = 5 就相當於:window.x = 5。之後 return this,也就是說 var x = a(5) 中的x變數的值是window,這裡的x將函式內部的x的值覆蓋了。然後執行console.log(x.x), 也就是console.log(window.x),而window物件中沒有x屬性,所以會輸出undefined。
- 當指向y.x時,會給全域性變數中的x賦值為6,所以會列印出6。
參考 前端進階面試題詳細解答
程式碼輸出結果
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
輸出結果: 10 2
解析:
- 第一次執行fn(),this指向window物件,輸出10。
- 第二次執行arguments[0],相當於arguments呼叫方法,this指向arguments,而這裡傳了兩個引數,故輸出arguments長度為2。
程式碼輸出結果
var friendName = 'World';
(function() {
if (typeof friendName === 'undefined') {
var friendName = 'Jack';
console.log('Goodbye ' + friendName);
} else {
console.log('Hello ' + friendName);
}
})();
輸出結果:Goodbye Jack
我們知道,在 JavaScript中, Function 和 var 都會被提升(變數提升),所以上面的程式碼就相當於:
var name = 'World!';
(function () {
var name;
if (typeof name === 'undefined') {
name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
這樣,答案就一目瞭然了。
程式碼輸出結果
Promise.resolve().then(() => {
console.log('1');
throw 'Error';
}).then(() => {
console.log('2');
}).catch(() => {
console.log('3');
throw 'Error';
}).then(() => {
console.log('4');
}).catch(() => {
console.log('5');
}).then(() => {
console.log('6');
});
執行結果如下:
1
3
5
6
在這道題目中,我們需要知道,無論是thne還是catch中,只要throw 丟擲了錯誤,就會被catch捕獲,如果沒有throw出錯誤,就被繼續執行後面的then。
程式碼輸出結果
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
輸出結果如下:
1
2
3
[1, 2, 3]
首先,定義了一個Promise,來非同步執行函式runAsync,該函式傳入一個值x,然後間隔一秒後列印出這個x。
之後再使用Promise.all
來執行這個函式,執行的時候,看到一秒之後輸出了1,2,3,同時輸出了陣列[1, 2, 3],三個函式是同步執行的,並且在一個回撥函式中返回了所有的結果。並且結果和函式的執行順序是一致的。
程式碼輸出結果
function Foo(){
Foo.a = function(){
console.log(1);
}
this.a = function(){
console.log(2)
}
}
Foo.prototype.a = function(){
console.log(3);
}
Foo.a = function(){
console.log(4);
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
輸出結果:4 2 1
解析:
- Foo.a() 這個是呼叫 Foo 函式的靜態方法 a,雖然 Foo 中有優先順序更高的屬性方法 a,但 Foo 此時沒有被呼叫,所以此時輸出 Foo 的靜態方法 a 的結果:4
- let obj = new Foo(); 使用了 new 方法呼叫了函式,返回了函式例項物件,此時 Foo 函式內部的屬性方法初始化,原型鏈建立。
- obj.a() ; 呼叫 obj 例項上的方法 a,該例項上目前有兩個 a 方法:一個是內部屬性方法,另一個是原型上的方法。當這兩者都存在時,首先查詢 ownProperty ,如果沒有才去原型鏈上找,所以呼叫例項上的 a 輸出:2
- Foo.a() ; 根據第2步可知 Foo 函式內部的屬性方法已初始化,覆蓋了同名的靜態方法,所以輸出:1
程式碼輸出結果
function foo(something){
this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2
var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
輸出結果: 2 2 3
這道題目和上面題目差不多,主要都是考察this繫結的優先順序。記住以下結論即可:this繫結的優先順序:new繫結 > 顯式繫結 > 隱式繫結 > 預設繫結。
程式碼輸出結果
function fn1(){
console.log('fn1')
}
var fn2
fn1()
fn2()
fn2 = function() {
console.log('fn2')
}
fn2()
輸出結果:
fn1
Uncaught TypeError: fn2 is not a function
fn2
這裡也是在考察變數提升,關鍵在於第一個fn2(),這時fn2仍是一個undefined的變數,所以會報錯fn2不是一個函式。
JS 隱式轉換,顯示轉換
一般非基礎型別進行轉換時會先呼叫 valueOf,如果 valueOf 無法返回基本型別值,就會呼叫 toString
字串和數字
- "+" 運算子,如果有一個為字串,那麼都轉化到字串然後執行字串拼接
- "-" 運算子,轉換為數字,相減 (-a, a * 1 a/1) 都能進行隱式強制型別轉換
[] + {} 和 {} + []
布林值到數字
- 1 + true = 2
- 1 + false = 1
轉換為布林值
- for 中第二個
- while
- if
- 三元表示式
- || (邏輯或) && (邏輯與)左邊的運算元
符號
- 不能被轉換為數字
- 能被轉換為布林值(都是 true)
- 可以被轉換成字串 "Symbol(cool)"
寬鬆相等和嚴格相等
寬鬆相等允許進行強制型別轉換,而嚴格相等不允許
字串與數字
轉換為數字然後比較
其他型別與布林型別
- 先把布林型別轉換為數字,然後繼續進行比較
物件與非物件
- 執行物件的 ToPrimitive(物件)然後繼續進行比較
假值列表
- undefined
- null
- false
- +0, -0, NaN
- ""
程式碼輸出結果
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function() {
console.log(this.foo);
console.log(self.foo);
}());
}
};
myObject.func();
輸出結果:bar bar undefined bar
解析:
- 首先func是由myObject呼叫的,this指向myObject。又因為var self = this;所以self指向myObject。
- 這個立即執行匿名函式表示式是由window呼叫的,this指向window 。立即執行匿名函式的作用域處於myObject.func的作用域中,在這個作用域找不到self變數,沿著作用域鏈向上查詢self變數,找到了指向 myObject物件的self。
說下對 JS 的瞭解吧
是基於原型的動態語言,主要獨特特性有 this、原型和原型鏈。
JS 嚴格意義上來說分為:語言標準部分(ECMAScript)+ 宿主環境部分
語言標準部分
2015 年釋出 ES6,引入諸多新特性使得能夠編寫大型專案變成可能,標準自 2015 之後以年號代號,每年一更
宿主環境部分
- 在瀏覽器宿主環境包括 DOM + BOM 等
- 在 Node,宿主環境包括一些檔案、資料庫、網路、與作業系統的互動等
程式碼輸出結果
var a = 10
var obj = {
a: 20,
say: () => {
console.log(this.a)
}
}
obj.say()
var anotherObj = { a: 30 }
obj.say.apply(anotherObj)
輸出結果:10 10
我麼知道,箭頭函式時不繫結this的,它的this來自原其父級所處的上下文,所以首先會列印全域性中的 a 的值10。後面雖然讓say方法指向了另外一個物件,但是仍不能改變箭頭函式的特性,它的this仍然是指向全域性的,所以依舊會輸出10。
但是,如果是普通函式,那麼就會有完全不一樣的結果:
var a = 10
var obj = {
a: 20,
say(){
console.log(this.a)
}
}
obj.say()
var anotherObj={a:30}
obj.say.apply(anotherObj)
輸出結果:20 30
這時,say方法中的this就會指向他所在的物件,輸出其中的a的值。
說一下你對盒模型的理解?
CSS3中的盒模型有以下兩種:標準盒模型、IE盒模型
盒模型都是由四個部分組成的,分別是margin、border、padding和content
標準盒模型和IE盒模型的區別在於設定width和height時, 所對應的範圍不同
1、標準盒模型的width和height屬性的範圍只包含了content
2、IE盒模型的width和height屬性的範圍包含了border、padding和content
可以透過修改元素的box-sizing屬性來改變元素的盒模型;
1、box-sizing:content-box表示標準盒模型(預設值)
2、box-sizing:border-box表示IE盒模型(怪異盒模型)
程式碼輸出結果
var obj = {
name : 'cuggz',
fun : function(){
console.log(this.name);
}
}
obj.fun() // cuggz
new obj.fun() // undefined
使用new建構函式時,其this指向的是全域性環境window。
事件迴圈機制 (Event Loop)
事件迴圈機制從整體上告訴了我們 JavaScript 程式碼的執行順序 Event Loop
即事件迴圈,是指瀏覽器或Node
的一種解決javaScript
單執行緒執行時不會阻塞的一種機制,也就是我們經常使用非同步的原理。
先執行 Script 指令碼,然後清空微任務佇列,然後開始下一輪事件迴圈,繼續先執行宏任務,再清空微任務佇列,如此往復。
- 宏任務:Script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
- 微任務:process.nextTick()/Promise
上訴的 setTimeout 和 setInterval 等都是任務源,真正進入任務佇列的是他們分發的任務。
優先順序
- setTimeout = setInterval 一個佇列
- setTimeout > setImmediate
- process.nextTick > Promise
for (const macroTask of macroTaskQueue) {
handleMacroTask();
for (const microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}