2020年前端面試題集錦(奧利給?。。。?/font>

基礎(chǔ)知識(shí)點(diǎn)與高頻考題
JavaScript基礎(chǔ)

console.log(1 < 2 < 3);
console.log(3 > 2 > 1);
// 寫出代碼執(zhí)行結(jié)果,并解釋為什么

// 答案與解析
true false
對(duì)于運(yùn)算符>、<,一般的計(jì)算從左向右
第一個(gè)題:1 < 2 等于 true, 然后true < 3,true == 1 ,因此結(jié)果是true
第二個(gè)題:3 > 2 等于 true, 然后true > 1, true == 1 ,因此結(jié)果是false

/*********************************/

[typeof null, null instanceof Object]
// 寫出代碼執(zhí)行的結(jié)果,并解釋為什么

//答案與解析
 ["object", false]
1)typeof操作符返回一個(gè)字符串,表示未經(jīng)計(jì)算的操作數(shù)的類型
    類型                    結(jié)果
Undefined        "undefined"
Null                "object"
Boolean            "boolean"
Number            "number"
String            "string"
Symbol            "symbol"
函數(shù)對(duì)象            "function"
任何其他對(duì)象    "object"
typeof null === 'object';// 從最開始的時(shí)候javascript就是這樣
JavaScript 中的值是由一個(gè)表示類型的標(biāo)簽和實(shí)際數(shù)據(jù)值表示的。對(duì)象的類型標(biāo)簽是 0。由于 null 代表的是空指針(大多數(shù)平臺(tái)下值為 0x00),因此,null的類型標(biāo)簽也成為了 0,typeof null就錯(cuò)誤的返回了"object"。這算一個(gè)bug,但是被拒絕修復(fù),因?yàn)橛绊懙膚eb系統(tǒng)太多

2)instanceof 運(yùn)算符用來(lái)測(cè)試一個(gè)對(duì)象在其原型鏈中是否存在一個(gè)構(gòu)造函數(shù)的 prototype 屬性
null不是以O(shè)bject原型創(chuàng)建,因此返回false

/*********************************/
// 死循環(huán)陷阱
var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
    count++;
}
console.log(count);
// 寫出正確的打印結(jié)果,并解釋為什么?

// 答案與解析
這個(gè)會(huì)陷入一個(gè)死循環(huán),2^53是最大的值,因此2^53+1 等于 2^53

/*********************************/

var a = Date(0);
var b = new Date(0);
var c = new Date();
[a === b, b === c, a === c];
// 寫出代碼執(zhí)行結(jié)果

// 答案與解析
 [false,false,false]
需要注意的是只能通過(guò)調(diào)用 Date 構(gòu)造函數(shù)來(lái)實(shí)例化日期對(duì)象:以常規(guī)函數(shù)調(diào)用它(即不加 new 操作符)將會(huì)返回一個(gè)字符串,而不是一個(gè)日期對(duì)象。另外,不像其他JavaScript 類型,Date 對(duì)象沒(méi)有字面量格式。
a是字符串,b和c是Date對(duì)象,并且b代表的是1970年那個(gè)初始化時(shí)間,而c代表的是當(dāng)前時(shí)間。

/*********************************/
// 逗號(hào)表達(dá)式
var x = 20;
var temp = {
    x: 40,
    foo: function() {
        var x = 10;
          console.log(this.x);
    }
};
(temp.foo, temp.foo)();

// 寫出打印結(jié)果
20
逗號(hào)操作符,逗號(hào)操作符會(huì)從左到右計(jì)算它的操作數(shù),返回最后一個(gè)操作數(shù)的值。所以(temp.foo, temp.foo)();等價(jià)于var fun = temp.foo; fun();,fun調(diào)用時(shí)this指向window,所以返回20。

/*********************************/
鏈?zhǔn)秸{(diào)用
// 實(shí)現(xiàn) (5).add(3).minus(2) 功能
// console.log((5).add(3).minus(2)); // 6

/*
    答案:12
    arguments中c的值還是1不會(huì)變成10, 
    因?yàn)閍函數(shù)加了默認(rèn)值,就按ES的方式解析,ES6是有塊級(jí)作用域的,所以c的值是不會(huì)改變的
*/
Number.prototype.add = function (number) {
    if (typeof number !== 'number') {
        throw new Error('請(qǐng)輸入數(shù)字~');
    }
    return this + number;
};
Number.prototype.minus = function (number) {
    if (typeof number !== 'number') {
        throw new Error('請(qǐng)輸入數(shù)字~');
    }
    return this - number;
};
console.log((5).add(3).minus(2));

/*********************************/

var a = 1;        
(function a () {            
    a = 2;            
    console.log(a);        
})();

// 答案
? a () {            
    a = 2;            
    console.log(a);        
}
/*
立即調(diào)用的函數(shù)表達(dá)式(IIFE) 有一個(gè) 自己獨(dú)立的 作用域,如果函數(shù)名稱與內(nèi)部變量名稱沖突,就會(huì)永遠(yuǎn)執(zhí)行函數(shù)本身;所以上面的結(jié)果輸出是函數(shù)本身;
*/

/*********************************/

var a = [0];
if(a){
    console.log(a == true);
}else{
    console.log(a);
}

/*
答案:false
當(dāng)a出現(xiàn)在if的條件中時(shí),被轉(zhuǎn)成布爾值,而Boolean([0])為true,所以就進(jìn)行下一步判斷 a == true,在進(jìn)行比較時(shí),[0]被轉(zhuǎn)換成了0,所以0==true為false
js的規(guī)則是:  
如果比較的是原始類型的值,原始類型的值會(huì)轉(zhuǎn)成數(shù)值再進(jìn)行比較
1 == true  //true   1 === Number(true)
'true' == true //false Number('true')->NaN  Number(true)->1
'' == 0//true
'1' == true//true  Number('1')->1
對(duì)象與原始類型值比較,對(duì)象會(huì)轉(zhuǎn)換成原始類型的值再進(jìn)行比較。
undefined和null與其它類型進(jìn)行比較時(shí),結(jié)果都為false,他們相互比較時(shí)結(jié)果為true。



下邊的不要了sadfasdfasdfasdfsadfasdffasdfasdf
一些隱式轉(zhuǎn)換
數(shù)組從非primitive轉(zhuǎn)為primitive的時(shí)候會(huì)先隱式調(diào)用join變成“0”,string和boolean比較的時(shí)候,兩個(gè)都先轉(zhuǎn)為number類型再比較,最后就是0==1的比較了
var a = [1];
if(a){
    console.log(a == true);
}else{
    console.log(a);
}
// true
!![] //true  空數(shù)組轉(zhuǎn)換為布爾值是true,  
!![0]//true  數(shù)組轉(zhuǎn)換為布爾值是true  
[0] == true;//false   數(shù)組與布爾值比較時(shí)卻變成了false  
Number([])//0  
Number(false)//0  
Number(['1'])//1

所以當(dāng)a出現(xiàn)在if的條件中時(shí),被轉(zhuǎn)成布爾值,而Boolean([0])為true,所以就進(jìn)行下一步判斷 a == true,在進(jìn)行比較時(shí),js的規(guī)則是:  
如果比較的是原始類型的值,原始類型的值會(huì)轉(zhuǎn)成數(shù)值再進(jìn)行比較
1 == true  //true   1 === Number(true)
'true' == true //false Number('true')->NaN  Number(true)->1
'' == 0//true
'1' == true//true  Number('1')->1
對(duì)象與原始類型值比較,對(duì)象會(huì)轉(zhuǎn)換成原始類型的值再進(jìn)行比較。
undefined和null與其它類型進(jìn)行比較時(shí),結(jié)果都為false,他們相互比較時(shí)結(jié)果為true。
*/

/****************************/

const a = [1,2,3],
    b = [1,2,3],
    c = [1,2,4],
        d = "2",
        e = "11";
console.log([a == b, a === b, a > c, a < c, d > e]);
// 寫出正確打印結(jié)果

// 答案
[false,false,false,true,true]

// 解析
1)JavaScript 有兩種比較方式:嚴(yán)格比較運(yùn)算符和轉(zhuǎn)換類型比較運(yùn)算符。
    對(duì)于嚴(yán)格比較運(yùn)算符(===)來(lái)說(shuō),僅當(dāng)兩個(gè)操作數(shù)的類型相同且值相等為 true,而對(duì)于被廣泛使用的比較運(yùn)算符(==)來(lái)說(shuō),會(huì)在進(jìn)行比較之前,將兩個(gè)操作數(shù)轉(zhuǎn)換成相同的類型。對(duì)于關(guān)系運(yùn)算符(比如 <=)來(lái)說(shuō),會(huì)先將操作數(shù)轉(zhuǎn)為原始值,使它們類型相同,再進(jìn)行比較運(yùn)算。
    當(dāng)兩個(gè)操作數(shù)都是對(duì)象時(shí),JavaScript會(huì)比較其內(nèi)部引用,當(dāng)且僅當(dāng)他們的引用指向內(nèi)存中的相同對(duì)象(區(qū)域)時(shí)才相等,即他們?cè)跅?nèi)存中的引用地址相同。
    javascript中Array也是對(duì)象,所以這里a,b,c顯然引用是不相同的,所以這里a==b,a===b都為false。

2)兩個(gè)數(shù)組進(jìn)行大小比較,也就是兩個(gè)對(duì)象進(jìn)行比較
    當(dāng)兩個(gè)對(duì)象進(jìn)行比較時(shí),會(huì)轉(zhuǎn)為原始類型的值,再進(jìn)行比較。對(duì)象轉(zhuǎn)換成原始類型的值,算法是先調(diào)用valueOf方法;如果返回的還是對(duì)象,再接著調(diào)用toString方法。
①valueOf() 方法返回指定對(duì)象的原始值。
  JavaScript調(diào)用valueOf方法將對(duì)象轉(zhuǎn)換為原始值。你很少需要自己調(diào)用valueOf方法;當(dāng)遇到要預(yù)期的原始值的對(duì)象時(shí),JavaScript會(huì)自動(dòng)調(diào)用它。默認(rèn)情況下,valueOf方法由Object后面的每個(gè)對(duì)象繼承。 每個(gè)內(nèi)置的核心對(duì)象都會(huì)覆蓋此方法以返回適當(dāng)?shù)闹?。如果?duì)象沒(méi)有原始值,則valueOf將返回對(duì)象本身。
②toString() 方法返回一個(gè)表示該對(duì)象的字符串。
  每個(gè)對(duì)象都有一個(gè) toString() 方法,當(dāng)該對(duì)象被表示為一個(gè)文本值時(shí),或者一個(gè)對(duì)象以預(yù)期的字符串方式引用時(shí)自動(dòng)調(diào)用。默認(rèn)情況下,toString() 方法被每個(gè) Object 對(duì)象繼承。如果此方法在自定義對(duì)象中未被覆蓋,toString() 返回 "[object type]",其中 type 是對(duì)象的類型。
③經(jīng)過(guò)valueOf,toString的處理,所以這里a,c最終會(huì)被轉(zhuǎn)換為"1,2,3"與"1,2,4";

3)兩個(gè)字符串進(jìn)行比較大小
    上邊的數(shù)組經(jīng)轉(zhuǎn)換為字符串之后,接著進(jìn)行大小比較。
    MDN中的描述是這樣的:字符串比較則是使用基于標(biāo)準(zhǔn)字典的 Unicode 值來(lái)進(jìn)行比較的。
    字符串按照字典順序進(jìn)行比較。JavaScript 引擎內(nèi)部首先比較首字符的 Unicode 碼點(diǎn)。如果相等,再比較第二個(gè)字符的 Unicode 碼點(diǎn),以此類推。
    所以這里 "1,2,3" < "1,2,4",輸出true,因?yàn)榍斑叺淖址膗nicode碼點(diǎn)都相等,所以最后是比較3和4的unicode碼點(diǎn)。而3的Unicode碼點(diǎn)是51,4的uniCode碼點(diǎn)是52,所以a<c。
"2" > "11"也是同理,這個(gè)也是開發(fā)中有時(shí)會(huì)遇到的問(wèn)題,所以在進(jìn)行運(yùn)算比較時(shí)需要注意一下。

4)關(guān)于valueOf,toString的調(diào)用順序
①javascript中對(duì)象到字符串的轉(zhuǎn)換經(jīng)歷的過(guò)程如下:
    如果對(duì)象具有toString()方法,javaScript會(huì)優(yōu)先調(diào)用此方法。如果返回的是一個(gè)原始值(原始值包括null、undefined、布爾值、字符串、數(shù)字),javaScript會(huì)將這個(gè)原始值轉(zhuǎn)換為字符串,并返回字符串作為結(jié)果。
    如果對(duì)象不具有toString()方法,或者調(diào)用toString()方法返回的不是原始值,則javaScript會(huì)判斷是否存在valueOf()方法,如若存在則調(diào)用此方法,如果返回的是原始值,javaScript會(huì)將原始值轉(zhuǎn)換為字符串作為結(jié)果。
  如果javaScript無(wú)法調(diào)用toString()和valueOf()返回原始值的時(shí)候,則會(huì)報(bào)一個(gè)類型錯(cuò)誤異常的警告。
    比如:String([1,2,3]);將一個(gè)對(duì)象轉(zhuǎn)換為字符串
    var a = [1,2,3];
    a.valueOf = function(){
    console.log("valueOf");
    return this
  }
    a.toString = function(){
    console.log('toString')
    return this
  }   
    String(a);
    因?yàn)檫@里我返回的是this,最后,所以如果javaScript無(wú)法調(diào)用toString()和valueOf()返回原始值的時(shí)候,則會(huì)報(bào)一個(gè)類型錯(cuò)誤異常的警告。
 
②javaScript中對(duì)象轉(zhuǎn)換為數(shù)字的轉(zhuǎn)換過(guò)程:
    javaScript優(yōu)先判斷對(duì)象是否具有valueOf()方法,如具有則調(diào)用,若返回一個(gè)原始值,javaScript會(huì)將原始值轉(zhuǎn)換為數(shù)字并作為結(jié)果。
    如果對(duì)象不具有valueOf()方法,javaScript則會(huì)調(diào)用toString()的方法,若返回的是原始值,javaScript會(huì)將原始值轉(zhuǎn)換為數(shù)字并作為結(jié)果。
    如果javaScript無(wú)法調(diào)用toString()和valueOf()返回原始值的時(shí)候,則會(huì)報(bào)一個(gè)類型錯(cuò)誤異常的警告。
    比如:Number([1,2,3]);將一個(gè)對(duì)象轉(zhuǎn)換為字符串
 
/*****************************************/
 
var a = ?;
if(a == 1 && a== 2 && a== 3){
     console.log(1);
}

/*
比較操作涉及多不同類型的值時(shí)候,會(huì)涉及到很多隱式轉(zhuǎn)換,其中規(guī)則繁多即便是經(jīng)驗(yàn)老道的程序員也沒(méi)辦法完全記住,特別是用到 `==` 和 `!=` 運(yùn)算時(shí)候。所以一些團(tuán)隊(duì)規(guī)定禁用 `==` 運(yùn)算符換用`===` 嚴(yán)格相等。
*/
// 答案一
var a? = 1;
var a = 2;
var ?a = 3;
if(a?==1 && a== 2 &&?a==3) {
    console.log("1")
}
/*
    考察你的找茬能力,注意if里面的空格,它是一個(gè)Unicode空格字符,不被ECMA腳本解釋為空格字符(這意味著它是標(biāo)識(shí)符的有效字符)。所以它可以解釋為
  var a_ = 1;
  var a = 2;
  var _a = 3;
  if(a_==1 && a== 2 &&_a==3) {
      console.log("1")
  }
*/
//答案二
var a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}
if(a == 1 && a == 2 && a == 3) {
  console.log('1');
}
/*
    如果原始類型的值和對(duì)象比較,對(duì)象會(huì)轉(zhuǎn)為原始類型的值,再進(jìn)行比較。
    對(duì)象轉(zhuǎn)換成原始類型的值,算法是先調(diào)用valueOf方法;如果返回的還是對(duì)象,再接著調(diào)用toString方法。
*/
// 答案三
var a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);
/*
    比較巧妙的方式,array也屬于對(duì)象,
    對(duì)于數(shù)組對(duì)象,toString 方法返回一個(gè)字符串,該字符串由數(shù)組中的每個(gè)元素的 toString() 返回值經(jīng)調(diào)用 join() 方法連接(由逗號(hào)隔開)組成。
    數(shù)組 toString 會(huì)調(diào)用本身的 join 方法,這里把自己的join方法該寫為shift,每次返回第一個(gè)元素,而且原數(shù)組刪除第一個(gè)值,正好可以使判斷成立
*/
// 答案四
var i = 0;
with({
  get a() {
    return ++i;
  }
}) {
  if (a == 1 && a == 2 && a == 3)
    console.log("1");
}
/*
    with 也是被嚴(yán)重建議不使用的對(duì)象,這里也是利用它的特性在代碼塊里面利用對(duì)象的 get 方法動(dòng)態(tài)返回 i.
*/
// 答案五
var val = 0;
Object.defineProperty(window, 'a', {
  get: function() {
    return ++val;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log('1');
}
/*
    全局變量也相當(dāng)于 window 對(duì)象上的一個(gè)屬性,這里用defineProperty 定義了 a的 get 也使得其動(dòng)態(tài)返回值。和with 有一些類似。
*/

// 答案六
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};
if (a == 1 && a == 2 && a == 3) {
  console.log('1');
}
/*
    ES6 引入了一種新的原始數(shù)據(jù)類型Symbol,表示獨(dú)一無(wú)二的值。我們之前在定義類的內(nèi)部私有屬性時(shí)候習(xí)慣用 __xxx ,這種命名方式避免別人定義相同的屬性名覆蓋原來(lái)的屬性,有了 Symbol  之后我們完全可以用 Symbol值來(lái)代替這種方法,而且完全不用擔(dān)心被覆蓋。
    除了定義自己使用的 Symbol 值以外,ES6 還提供了 11 個(gè)內(nèi)置的 Symbol 值,指向語(yǔ)言內(nèi)部使用的方法。Symbol.toPrimitive就是其中一個(gè),它指向一個(gè)方法,表示該對(duì)象被轉(zhuǎn)為原始類型的值時(shí),會(huì)調(diào)用這個(gè)方法,返回該對(duì)象對(duì)應(yīng)的原始類型值。這里就是改變這個(gè)屬性,把它的值改為一個(gè) 閉包 返回的函數(shù)。
*/

業(yè)務(wù)中一般不會(huì)寫出這種代碼,重點(diǎn)還是知識(shí)點(diǎn)的考察

/*******************************************/

let a = {n: 1};
let b = a;
a.x = a = {n: 2};
console.log(a.x)    
console.log(b.x)

答案:
undefined {n:2}

注意點(diǎn):

1: 點(diǎn)的優(yōu)先級(jí)大于等號(hào)的優(yōu)先級(jí)
2: 對(duì)象以指針的形式進(jìn)行存儲(chǔ),每個(gè)新對(duì)象都是一份新的存儲(chǔ)地址

解析:

- `var b = a;` b 和 a 都指向同一個(gè)地址。
- `.`的優(yōu)先級(jí)高于`=`。所以先執(zhí)行`a.x`,于是現(xiàn)在的`a`和`b`都是`{n: 1, x: undefined}`。
- `=`是從右向左執(zhí)行。所以是執(zhí)行 `a = {n: 2}`,于是`a`指向了`{n: 2}`
- 再執(zhí)行 `a.x = a`。 這里注意,`a.x` 是最開始執(zhí)行的,已經(jīng)是`{n: 1, x: undefined}`這個(gè)地址了,而不是一開的的那個(gè)`a`,所以也就不是`{n: 2}`了。而且`b`和舊的`a`是指向一個(gè)地址的,所以`b`也改變了。
- 但是,`=`右面的a,是已經(jīng)指向了新地址的新`a`。
- 所以,`a.x = a` 可以看成是`{n: 1, x: undefined}.x = {n: 2}`
- 最終得出
  a = { n: 2 },
  b = {
     n: 1,
     x: { n: 2 }
  }

/*****************************************/

// 實(shí)現(xiàn)一個(gè)模板引擎
let template = '我是{{name}},年齡{{age}},性別{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年齡18,性別undefined
function render(template,data){
  // your code
}
// 補(bǔ)充代碼,使代碼可以正確執(zhí)行

// 代碼實(shí)現(xiàn)
function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正則
  if (reg.test(template)) { // 判斷模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段
    template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染
    return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu)
  }
  return template; // 如果模板沒(méi)有模板字符串直接返回
}

/*****************************************/

什么是包裝對(duì)象(wrapper object)?

// 答案
1)復(fù)習(xí)一下JS的數(shù)據(jù)類型,JS數(shù)據(jù)類型被分為兩大類,基本類型和引用類型
①基本類型:Undefined,Null,Boolean,Number,String,Symbol,BigInt
②引用類型:Object,Array,Date,RegExp等,說(shuō)白了就是對(duì)象。

2)其中引用類型有方法和屬性,但是基本類型是沒(méi)有的,但我們經(jīng)常會(huì)看到下面的代碼
let name = "marko";
console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"
name類型是 string,屬于基本類型,所以它沒(méi)有屬性和方法,但是在這個(gè)例子中,我們調(diào)用了一個(gè)toUpperCase()方法,它不會(huì)拋出錯(cuò)誤,還返回了對(duì)象的變量值。
原因是基本類型的值被臨時(shí)轉(zhuǎn)換或強(qiáng)制轉(zhuǎn)換為對(duì)象,因此name變量的行為類似于對(duì)象。 除null和undefined之外的每個(gè)基本類型都有自己包裝對(duì)象。也就是:String,Number,Boolean,Symbol和BigInt。 在這種情況下,name.toUpperCase()在幕后看起來(lái)如下:
console.log(new String(name).toUpperCase()); // "MARKO"
在完成訪問(wèn)屬性或調(diào)用方法之后,新創(chuàng)建的對(duì)象將立即被丟棄。



防抖/節(jié)流

const debounce = (fn,delay) => {
  // 介紹防抖函數(shù)原理,并實(shí)現(xiàn)
  // your code
}
const throttle = (fn,delay = 500) => {
  // 介紹節(jié)流函數(shù)原理,并實(shí)現(xiàn)
   // your code
}

// 答案與解析
/*
    1)防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。
    適用場(chǎng)景:
        ①按鈕提交場(chǎng)景:防止多次提交按鈕,只執(zhí)行最后提交的一次
        ②服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)行一段連續(xù)的輸入事件的最后一次,還有搜索聯(lián)想詞功能類似
*/
// 手寫簡(jiǎn)化版實(shí)現(xiàn)
const debounce = (fn,delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this.args);
    },delay)
  }
}

/*
    2)節(jié)流函數(shù)原理:規(guī)定在一個(gè)單位時(shí)間內(nèi),只能觸發(fā)一次函數(shù)。如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只有一次生效。防抖是延遲執(zhí)行,而節(jié)流是間隔執(zhí)行,函數(shù)節(jié)流即每隔一段時(shí)間就執(zhí)行一次
    適用場(chǎng)景:
        ①拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)行一次,防止超高頻次觸發(fā)位置變動(dòng)
        ②縮放場(chǎng)景:監(jiān)控瀏覽器resize
        ③動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問(wèn)題
*/
// 手寫簡(jiǎn)化版實(shí)現(xiàn)
// ①定時(shí)器實(shí)現(xiàn)
const throttle = (fn,delay = 500) =>{
  let flag = true;
  return (...args) => {
    if(!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this,args);
      flag = true;
    },delay);
  };
}
// ②時(shí)間戳實(shí)現(xiàn)
const throttle = (fn,delay = 500) => {
  let preTime = Date.now();
  return (...args) => {
    const nowTime = Date.now();
    if(nowTime - preTime >= delay){
          preTime = Date.now();
          fn.apply(this,args);
    }
  }
}



new 的實(shí)現(xiàn)原理,模擬實(shí)現(xiàn)一下

// 首先,new操作符做了什么
new 運(yùn)算符創(chuàng)建一個(gè)用戶定義的對(duì)象類型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對(duì)象的實(shí)例。new 關(guān)鍵字會(huì)進(jìn)行如下的操作
創(chuàng)建一個(gè)空的簡(jiǎn)單JavaScript對(duì)象(即{});
鏈接該對(duì)象(即設(shè)置該對(duì)象的構(gòu)造函數(shù))到另一個(gè)對(duì)象 ;
將步驟1新創(chuàng)建的對(duì)象作為this的上下文 ;
如果該函數(shù)沒(méi)有返回對(duì)象,則返回this。即創(chuàng)建的這個(gè)的新對(duì)象,否則,返回構(gòu)造函數(shù)中返回的對(duì)象。

// 參考答案:1.簡(jiǎn)單實(shí)現(xiàn)
function newOperator(ctor) {
    if (typeof ctor !== 'function'){
        throw 'newOperator function the first param must be a function';
    }
      // ES5 arguments轉(zhuǎn)成數(shù)組 當(dāng)然也可以用ES6 [...arguments], Aarry.from(arguments);
    var args = Array.prototype.slice.call(arguments, 1);
    // 1.創(chuàng)建一個(gè)空的簡(jiǎn)單JavaScript對(duì)象(即{})
    var obj = {};
    // 2.鏈接該新創(chuàng)建的對(duì)象(即設(shè)置該對(duì)象的__proto__)到該函數(shù)的原型對(duì)象prototype上
    obj.__proto__ = ctor.prototype;
    // 3.將步驟1新創(chuàng)建的對(duì)象作為this的上下文
    var result = ctor.apply(obj, args);
    // 4.如果該函數(shù)沒(méi)有返回對(duì)象,則返回新創(chuàng)建的對(duì)象

    var isObject = typeof result === 'object' && result !== null;
    var isFunction = typeof result === 'function';
    return isObject || isFunction ? result : obj;
}

// 測(cè)試
function company(name, address) {
    this.name = name;
    this.address = address;
  }
 
var company1 = newOperator(company, 'yideng', 'beijing');
console.log('company1: ', company1);

// 參考答案:2.更完整的實(shí)現(xiàn)
/**
 * 模擬實(shí)現(xiàn) new 操作符
 * @param  {Function} ctor [構(gòu)造函數(shù)]
 * @return {Object|Function|Regex|Date|Error}      [返回結(jié)果]
 */
function newOperator(ctor){
    if(typeof ctor !== 'function'){
      throw 'newOperator function the first param must be a function';
    }
    // ES6 new.target 是指向構(gòu)造函數(shù)
    newOperator.target = ctor;
    // 1.創(chuàng)建一個(gè)全新的對(duì)象,
    // 2.并且執(zhí)行[[Prototype]]鏈接
    // 4.通過(guò)`new`創(chuàng)建的每個(gè)對(duì)象將最終被`[[Prototype]]`鏈接到這個(gè)函數(shù)的`prototype`對(duì)象上。
    var newObj = Object.create(ctor.prototype);
    // ES5 arguments轉(zhuǎn)成數(shù)組 當(dāng)然也可以用ES6 [...arguments], Aarry.from(arguments);
    // 除去ctor構(gòu)造函數(shù)的其余參數(shù)
    var argsArr = [].slice.call(arguments, 1);
    // 3.生成的新對(duì)象會(huì)綁定到函數(shù)調(diào)用的`this`。
    // 獲取到ctor函數(shù)返回結(jié)果
    var ctorReturnResult = ctor.apply(newObj, argsArr);
    // 小結(jié)4 這些類型中合并起來(lái)只有Object和Function兩種類型 typeof null 也是'object'所以要不等于null,排除null
    var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
    var isFunction = typeof ctorReturnResult === 'function';
    if(isObject || isFunction){
        return ctorReturnResult;
    }
    // 5.如果函數(shù)沒(méi)有返回對(duì)象類型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新的對(duì)象。
    return newObj;
}


this指向

// 來(lái)一道面試題
var a=10;
var foo={
  a:20,
  bar:function(){
      var a=30;
      return this.a;
    }
}
console.log(foo.bar());
console.log((foo.bar)());
console.log((foo.bar=foo.bar)());
console.log((foo.bar,foo.bar)());

// 答案:
20 20 10 10

// 第一問(wèn)  foo.bar()
/*
    foo調(diào)用,this指向foo , 此時(shí)的 this 指的是foo,輸出20
*/
// 第二問(wèn)  (foo.bar)()
/*
    給表達(dá)式加了括號(hào),而括號(hào)的作用是改變表達(dá)式的運(yùn)算順序,而在這里加與不加括號(hào)并無(wú)影響;相當(dāng)于foo.bar(),輸出20
*/
// 第三問(wèn)  (foo.bar=foo.bar)()
/*
    等號(hào)運(yùn)算,
    相當(dāng)于重新給foo.bar定義,即
    foo.bar = function () {
    var a = 10;
    return this.a;
    }
    就是普通的復(fù)制,一個(gè)匿名函數(shù)賦值給一個(gè)全局變量
    所以這個(gè)時(shí)候foo.bar是在window作用域下而不是foo = {}這一塊級(jí)作用域,所以這里的this指代的是window,輸出10
*/
// 第四問(wèn)  (foo.bar,foo.bar)()
/*
    1.逗號(hào)運(yùn)算符,
    2.逗號(hào)表達(dá)式,求解過(guò)程是:先計(jì)算表達(dá)式1的值,再計(jì)算表達(dá)式2的值,……一直計(jì)算到表達(dá)式n的值。最后整個(gè)逗號(hào)表達(dá)式的值是表達(dá)式n的值。逗號(hào)運(yùn)算符的返回值是最后一個(gè)表達(dá)式的值。
  3.其實(shí)這里主要還是經(jīng)過(guò)逗號(hào)運(yùn)算符后,就是純粹的函數(shù)了,不是對(duì)象方法的引用,所以這里this指向的是window,輸出10
  4.第三問(wèn),第四問(wèn),一個(gè)是等號(hào)運(yùn)算,一個(gè)是逗號(hào)運(yùn)算,可以這么理解,經(jīng)過(guò)賦值,運(yùn)算符運(yùn)算后,都是純粹的函數(shù),不是對(duì)象方法的引用。所以函數(shù)指向的this都是windows的。
*/



如果用一句話說(shuō)明 this 的指向,那么即是: 誰(shuí)調(diào)用它,this 就指向誰(shuí)。但是僅通過(guò)這句話,我們很多時(shí)候并不能準(zhǔn)確判斷 this 的指向。因此我們需要借助一些規(guī)則去幫助自己:

首先來(lái)看一下this綁定的規(guī)則,來(lái)詳細(xì)看一下,這樣再遇到this的問(wèn)題,可以從容應(yīng)對(duì)

    默認(rèn)綁定

        // 默認(rèn)綁定,在不能應(yīng)用其它綁定規(guī)則時(shí)使用的默認(rèn)規(guī)則,通常是獨(dú)立函數(shù)調(diào)用。
        function sayHi(){
            console.log('Hello,', this.name);
        }
        var name = 'yideng';
        sayHi();
        //在調(diào)用Hi()時(shí),應(yīng)用了默認(rèn)綁定,this指向全局對(duì)象(非嚴(yán)格模式下),嚴(yán)格模式下,this指向undefined,undefined上沒(méi)有this對(duì)象,會(huì)拋出錯(cuò)誤。
        // 如果在瀏覽器環(huán)境中運(yùn)行,那么結(jié)果就是 Hello,yideng
        // 如果在node環(huán)境中運(yùn)行,結(jié)果就是 Hello,undefined.這是因?yàn)閚ode中name并不是掛在全局對(duì)象上的。


    隱式綁定

        // 函數(shù)的調(diào)用是在某個(gè)對(duì)象上觸發(fā)的,即調(diào)用位置上存在上下文對(duì)象。典型的形式為 XXX.fun()
        function sayHi(){
            console.log('Hello,', this.name);
        }
        var person = {
            name: 'yidneg1',
            sayHi: sayHi
        }
        var name = 'yidneg2';
        person.sayHi();
        // Hello yideng1
        // sayHi函數(shù)聲明在外部,嚴(yán)格來(lái)說(shuō)并不屬于person,但是在調(diào)用sayHi時(shí),調(diào)用位置會(huì)使用person的上下文來(lái)引用函數(shù),隱式綁定會(huì)把函數(shù)調(diào)用中的this(即此例sayHi函數(shù)中的this)綁定到這個(gè)上下文對(duì)象(即此例中的person)


        需要注意的是:對(duì)象屬性鏈中只有最后一層會(huì)影響到調(diào)用位置。

        function sayHi(){
            console.log('Hello,', this.name);
        }
        var person2 = {
            name: 'yideng1',
            sayHi: sayHi
        }
        var person1 = {
            name: 'yideng2',
            friend: person2
        }
        person1.friend.sayHi();
        // Hello yideng1
        //因?yàn)橹挥凶詈笠粚訒?huì)確定this指向的是什么,不管有多少層,在判斷this的時(shí)候,我們只關(guān)注最后一層,即此處的friend。


        **隱式綁定有一個(gè)大陷阱,**綁定很容易丟失(或者說(shuō)容易給我們?cè)斐烧`導(dǎo),我們以為this指向的是什么,但是實(shí)際上并非如此).

        function sayHi(){
            console.log('Hello,', this.name);
        }
        var person = {
            name: 'yideng1',
            sayHi: sayHi
        }
        var name = 'yideng2';
        var Hi = person.sayHi;
        Hi();
        // Htllo yideng2
        // Hi直接指向了sayHi的引用,在調(diào)用的時(shí)候,跟person沒(méi)有半毛錢的關(guān)系,針對(duì)此類問(wèn)題,我建議大家只需牢牢記住這個(gè)格式:XXX.fn();fn()前如果什么都沒(méi)有,那么肯定不是隱式綁定。


        除了上面這種丟失之外,隱式綁定的丟失是發(fā)生在回調(diào)函數(shù)中(事件回調(diào)也是其中一種),

        function sayHi(){
            console.log('Hello,', this.name);
        }
        var person1 = {
            name: 'yideng1',
            sayHi: function(){
                setTimeout(function(){
                    console.log('Hello,',this.name);
                })
            }
        }
        var person2 = {
            name: 'yideng2',
            sayHi: sayHi
        }
        var name='yideng3';
        person1.sayHi();
        setTimeout(person2.sayHi,100);
        setTimeout(function(){
            person2.sayHi();
        },200);

        // Hello yideng3
        // Hello yideng3
        // Hello yideng2

        1.第一條輸出很容易理解,setTimeout的回調(diào)函數(shù)中,this使用的是默認(rèn)綁定,非嚴(yán)格模式下,執(zhí)行的是全局對(duì)象
        2.第二條輸出是不是有點(diǎn)迷惑了?說(shuō)好XXX.fun()的時(shí)候,fun中的this指向的是XXX呢,為什么這次卻不是這樣了!Why?
        其實(shí)這里我們可以這樣理解: setTimeout(fn,delay){ fn(); },相當(dāng)于是將person2.sayHi賦值給了一個(gè)變量,最后執(zhí)行了變量,這個(gè)時(shí)候,sayHi中的this顯然和person2就沒(méi)有關(guān)系了。
        3.第三條雖然也是在setTimeout的回調(diào)中,但是我們可以看出,這是執(zhí)行的是person2.sayHi()使用的是隱式綁定,因此這是this指向的是person2,跟當(dāng)前的作用域沒(méi)有任何關(guān)系。


        看到這里是不是有點(diǎn)疲倦了

    顯示綁定

        顯式綁定比較好理解,就是通過(guò)call,apply,bind的方式,顯式的指定this所指向的對(duì)象。

        call,apply和bind的第一個(gè)參數(shù),就是對(duì)應(yīng)函數(shù)的this所指向的對(duì)象。call和apply的作用一樣,只是傳參方式不同。call和apply都會(huì)執(zhí)行對(duì)應(yīng)的函數(shù),而bind方法不會(huì)。

        function sayHi(){
            console.log('Hello,', this.name);
        }
        var person = {
            name: 'yideng1',
            sayHi: sayHi
        }
        var name = 'yideng2';
        var Hi = person.sayHi;
        Hi.call(person); //Hi.apply(person)
        // Hello yideng1  因?yàn)槭褂糜步壎鞔_將this綁定在了person上。


        使用了硬綁定,是不是意味著不會(huì)出現(xiàn)隱式綁定所遇到的綁定丟失呢?顯然不是這樣的,不信,繼續(xù)往下看。

        function sayHi(){
            console.log('Hello,', this.name);
        }
        var person = {
            name: 'yideng1',
            sayHi: sayHi
        }
        var name = 'yideng2';
        var Hi = function(fn) {
            fn();
        }
        Hi.call(person, person.sayHi);
        // Hello yideng2
        輸出的結(jié)果是 Hello, Wiliam. 原因很簡(jiǎn)單,Hi.call(person, person.sayHi)的確是將this綁定到Hi中的this了。但是在執(zhí)行fn的時(shí)候,相當(dāng)于直接調(diào)用了sayHi方法(記住: person.sayHi已經(jīng)被賦值給fn了,隱式綁定也丟了),沒(méi)有指定this的值,對(duì)應(yīng)的是默認(rèn)綁定。
        現(xiàn)在,我們希望綁定不會(huì)丟失,要怎么做?很簡(jiǎn)單,調(diào)用fn的時(shí)候,也給它硬綁定。

        var Hi = function(fn) {
            fn.call(this);
        }
        這樣就行了
        因?yàn)閜erson被綁定到Hi函數(shù)中的this上,fn又將這個(gè)對(duì)象綁定給了sayHi的函數(shù)。這時(shí),sayHi中的this指向的就是person對(duì)象。


    new 綁定

        javaScript和C++不一樣,并沒(méi)有類,在javaScript中,構(gòu)造函數(shù)只是使用new操作符時(shí)被調(diào)用的函數(shù),這些函數(shù)和普通的函數(shù)并沒(méi)有什么不同,它不屬于某個(gè)類,也不可能實(shí)例化出一個(gè)類。任何一個(gè)函數(shù)都可以使用new來(lái)調(diào)用,因此其實(shí)并不存在構(gòu)造函數(shù),而只有對(duì)于函數(shù)的“構(gòu)造調(diào)用”

        前邊我們提到new 操作符都干了什么
            創(chuàng)建一個(gè)空對(duì)象,構(gòu)造函數(shù)中的this指向這個(gè)空對(duì)象
            這個(gè)新對(duì)象被執(zhí)行 [[原型]] 連接
            執(zhí)行構(gòu)造函數(shù)方法,屬性和方法被添加到this引用的對(duì)象中
            如果構(gòu)造函數(shù)中沒(méi)有返回其它對(duì)象,那么返回this,即創(chuàng)建的這個(gè)的新對(duì)象,否則,返回構(gòu)造函數(shù)中返回的對(duì)象。

        因此,我們使用new來(lái)調(diào)用函數(shù)的時(shí)候,就會(huì)新對(duì)象綁定到這個(gè)函數(shù)的this上。

        function sayHi(name){
            this.name = name;
           
        }
        var Hi = new sayHi('yideng');
        console.log('Hello,', Hi.name); // Hello yideng

    綁定優(yōu)先級(jí)
        我們知道了this有四種綁定規(guī)則,但是如果同時(shí)應(yīng)用了多種規(guī)則,怎么辦?
        顯然,我們需要了解哪一種綁定方式的優(yōu)先級(jí)更高,這四種綁定的優(yōu)先級(jí)為:
            new綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定
            感興趣的可以寫個(gè)demo測(cè)試看看

    綁定例外

        凡事都有例外,this的規(guī)則也是這樣。

        如果我們將null或者是undefined作為this的綁定對(duì)象傳入call、apply或者是bind,這些值在調(diào)用時(shí)會(huì)被忽略,實(shí)際應(yīng)用的是默認(rèn)綁定規(guī)則。

        var foo = {
            name: 'yideng1'
        }
        var name = 'yideng2';
        function bar() {
            console.log(this.name);
        }
        bar.call(null); //yideng2
        因?yàn)檫@時(shí)實(shí)際應(yīng)用的是默認(rèn)綁定規(guī)則。


    箭頭函數(shù)
        箭頭函數(shù)是ES6中新增的,它和普通函數(shù)有一些區(qū)別,箭頭函數(shù)沒(méi)有自己的this,它的this繼承于外層代碼庫(kù)中的this。箭頭函數(shù)在使用時(shí),需要注意以下幾點(diǎn):
            函數(shù)體內(nèi)的this對(duì)象,繼承的是外層代碼塊的this。
            不可以當(dāng)作構(gòu)造函數(shù),也就是說(shuō),不可以使用new命令,否則會(huì)拋出一個(gè)錯(cuò)誤。
            不可以使用arguments對(duì)象,該對(duì)象在函數(shù)體內(nèi)不存在。如果要用,可以用 rest 參數(shù)代替。
            不可以使用yield命令,因此箭頭函數(shù)不能用作 Generator 函數(shù)。
            箭頭函數(shù)沒(méi)有自己的this,所以不能用call()、apply()、bind()這些方法去改變this的指向.

一道綜合面試題

function Foo() {
    getName = function () { console.log(1); };
    return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log (4);};
function getName() { console.log (5);}
 
//請(qǐng)寫出以下輸出結(jié)果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

// 2 4 1 1 2 3 3


這道題的經(jīng)典之處在于它綜合考察了面試者的JavaScript的綜合能力,包含了變量定義提升、this指針指向、運(yùn)算符優(yōu)先級(jí)、原型、繼承、全局變量污染、對(duì)象屬性及原型屬性優(yōu)先級(jí)等知識(shí)

先看此題的上半部分做了什么,

    首先定義了一個(gè)叫Foo的函數(shù),
    之后為Foo創(chuàng)建了一個(gè)叫g(shù)etName的靜態(tài)屬性存儲(chǔ)了一個(gè)匿名函數(shù),
    之后為Foo的原型對(duì)象新創(chuàng)建了一個(gè)叫g(shù)etName的匿名函數(shù)。
    之后又通過(guò)函數(shù)變量表達(dá)式創(chuàng)建了一個(gè)getName的函數(shù),
    最后再聲明一個(gè)叫g(shù)etName函數(shù)。

第一問(wèn):Foo.getName

自然是訪問(wèn)Foo函數(shù)上存儲(chǔ)的靜態(tài)屬性,答案自然是2,一般來(lái)說(shuō)第一問(wèn)對(duì)于稍微懂JS基礎(chǔ)的同學(xué)來(lái)說(shuō)應(yīng)該是沒(méi)問(wèn)題的

// 回顧下基礎(chǔ)知識(shí)
function User(name) {
    var name = name; //私有屬性
    this.name = name; //公有屬性
    function getName() { //私有方法
        return name;
    }
}
User.prototype.getName = function() { //公有方法
    return this.name;
}
User.name = 'yideng'; //靜態(tài)屬性
User.getName = function() { //靜態(tài)方法
    return this.name;
}
var yideng = new User('yideng'); //實(shí)例化



幾個(gè)注意點(diǎn):

    調(diào)用公有方法,公有屬性,我們必需先實(shí)例化對(duì)象,也就是用new操作符實(shí)化對(duì)象,就可構(gòu)造函數(shù)實(shí)例化對(duì)象的方法和屬性,并且公有方法是不能調(diào)用私有方法和靜態(tài)方法的
    靜態(tài)方法和靜態(tài)屬性就是我們無(wú)需實(shí)例化就可以調(diào)用
    而對(duì)象的私有方法和屬性,外部是不可以訪問(wèn)的

第二問(wèn):getName();

直接調(diào)用getName函數(shù)。既然是直接調(diào)用那么就是訪問(wèn)當(dāng)前上文作用域內(nèi)的叫g(shù)etName的函數(shù),所以這里應(yīng)該直接把關(guān)注點(diǎn)放在4和5上,跟1 2 3都沒(méi)什么關(guān)系。

那這里是輸出4還是5呢?-----------互動(dòng)

這里就有兩個(gè)坑了

    一是變量聲明提升
        JavaScript 解釋器中存在一種變量聲明被提升的機(jī)制,也就是說(shuō)函數(shù)聲明會(huì)被提升到作用域的最前面,即使寫代碼的時(shí)候是寫在最后面,也還是會(huì)被提升至最前面。

    二是函數(shù)表達(dá)式和函數(shù)聲明的區(qū)別
        函數(shù)聲明在JS解析時(shí)進(jìn)行函數(shù)提升,因此在同一個(gè)作用域內(nèi),不管函數(shù)聲明在哪里定義,該函數(shù)都可以進(jìn)行調(diào)用。而函數(shù)表達(dá)式的值是在JS運(yùn)行時(shí)確定,并且在表達(dá)式賦值完成后,該函數(shù)才能調(diào)用。

    // 直觀的例子
    getName();// 函數(shù)聲明
    var getName = function(){
      console.log('函數(shù)表達(dá)式')
    }
    getName();// 函數(shù)表達(dá)式
    function getName(){
      console.log('函數(shù)聲明');
    }
    getName(); // 函數(shù)表達(dá)式


    所以第二問(wèn)的答案就是4,5的函數(shù)聲明被4的函數(shù)表達(dá)式覆蓋了

第三問(wèn):Foo().getName()

先執(zhí)行了Foo函數(shù),然后調(diào)用Foo函數(shù)的返回值對(duì)象的getName屬性函數(shù)

Foo函數(shù)的第一句getName = function () { console.log(1); };是一句函數(shù)賦值語(yǔ)句,注意它沒(méi)有var聲明,

所以先向當(dāng)前Foo函數(shù)作用域內(nèi)尋找getName變量,沒(méi)有。再向當(dāng)前函數(shù)作用域上層,即外層作用域內(nèi)尋找是否含有g(shù)etName變量,找到了,也就是第二問(wèn)中的console.log(4)函數(shù),將此變量的值賦值為function(){console.log(1)}。

此處實(shí)際上是將外層作用域內(nèi)的getName函數(shù)修改了。

    注意:此處若依然沒(méi)有找到會(huì)一直向上查找到window對(duì)象,若window對(duì)象中也沒(méi)有g(shù)etName屬性,就在window對(duì)象中創(chuàng)建一個(gè)getName變量。

之后Foo函數(shù)的返回值是this,this的指向是由所在函數(shù)的調(diào)用方式?jīng)Q定的。而此處的直接調(diào)用方式,this指向window對(duì)象。

所以Foo函數(shù)返回的是window對(duì)象,相當(dāng)于執(zhí)行window.getName(),而window中的getName已經(jīng)被修改為alert(1),所以最終會(huì)輸出1

此處考察了兩個(gè)知識(shí)點(diǎn),一個(gè)是變量作用域問(wèn)題,一個(gè)是this指向問(wèn)題

關(guān)于this,this的指向在函數(shù)定義的時(shí)候是確定不了的,只有函數(shù)執(zhí)行的時(shí)候才能確定this到底指向誰(shuí),實(shí)際上this的最終指向的是那個(gè)調(diào)用它的對(duì)象

所以第三問(wèn)中實(shí)際上就是window在調(diào)用**Foo()**函數(shù),所以this的指向是window

第四問(wèn):getName()

直接調(diào)用getName函數(shù),相當(dāng)于window.getName(),因?yàn)檫@個(gè)變量已經(jīng)被Foo函數(shù)執(zhí)行時(shí)修改了,遂結(jié)果與第三問(wèn)相同,為1,也就是說(shuō)Foo執(zhí)行后把全局的getName函數(shù)給重寫了一次,所以結(jié)果就是Foo()執(zhí)行重寫的那個(gè)getName函數(shù)

第五問(wèn):new Foo.getName()

此處考察的是JS的運(yùn)算符優(yōu)先級(jí)問(wèn)題

MDN運(yùn)算符優(yōu)先級(jí):https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

這題首先看優(yōu)先級(jí)的第18和第17都出現(xiàn)關(guān)于new的優(yōu)先級(jí),new (帶參數(shù)列表)比new (無(wú)參數(shù)列表)高比函數(shù)調(diào)用高,跟成員訪問(wèn)同級(jí)

new Foo.getName();的優(yōu)先級(jí)是這樣的
相當(dāng)于是:
new (Foo.getName)();

// 點(diǎn)的優(yōu)先級(jí)(18)比new無(wú)參數(shù)列表(17)優(yōu)先級(jí)高
// 當(dāng)點(diǎn)運(yùn)算完后又因?yàn)橛袀€(gè)括號(hào)(),此時(shí)就是變成new有參數(shù)列表(18),所以直接執(zhí)行new,當(dāng)然也可能有朋友會(huì)有疑問(wèn)為什么遇到()不函數(shù)調(diào)用再new呢,那是因?yàn)楹瘮?shù)調(diào)用(17)比new有參數(shù)列表(18)優(yōu)先級(jí)低

.成員訪問(wèn)(18)->new有參數(shù)列表(18)
所以這里實(shí)際上將getName函數(shù)作為了構(gòu)造函數(shù)來(lái)執(zhí)行,遂彈出2。


第六問(wèn):(new Foo()).getName()

這一題比上一題的唯一區(qū)別就是在Foo那里多出了一個(gè)括號(hào),這個(gè)有括號(hào)跟沒(méi)括號(hào)我們?cè)诘谖鍐?wèn)的時(shí)候也看出來(lái)優(yōu)先級(jí)是有區(qū)別的

1.與第四問(wèn)的區(qū)別就是有括號(hào)無(wú)括號(hào),這里帶括號(hào)是new 有參數(shù)列表,new 有參數(shù)列表的優(yōu)先級(jí)是18,點(diǎn)的優(yōu)先級(jí)也是18,優(yōu)先級(jí)相同按從左到右的順序執(zhí)行。
    2.所以這里是先執(zhí)行有參數(shù)列表,再執(zhí)行點(diǎn)的優(yōu)先級(jí),最后再函數(shù)調(diào)用
    3.這里涉及到一個(gè)知識(shí)點(diǎn),fn作為構(gòu)造函數(shù)有返回值,在JS中構(gòu)造函數(shù)可以有返回值也可以沒(méi)有
        a.沒(méi)有返回值,返回實(shí)例化的對(duì)象
        b.有返回值,檢查其返回值是否為引用類型。
            非引用類型,如基本類型(String,Number,Boolean,Null,Undefined)則與無(wú)返回值相同,實(shí)際返回其實(shí)例化對(duì)象。
            引用類型,實(shí)際返回值為這個(gè)引用類型
    4.這里fn函數(shù)返回的是this,this在構(gòu)造函數(shù)中本來(lái)就代表當(dāng)前實(shí)例化對(duì)象,最終fn函數(shù)返回實(shí)例化對(duì)象。最終調(diào)用,實(shí)例化對(duì)象的getValue函數(shù),因?yàn)樵贔oo構(gòu)造函數(shù)中沒(méi)有為實(shí)例化對(duì)象添加任何屬性,當(dāng)前對(duì)象的原型對(duì)象(prototype)中尋找getName函數(shù)。所以輸出3。   



第七問(wèn):new new Foo().getName();

還有優(yōu)先級(jí)的問(wèn)題,到這里其實(shí)答案已經(jīng)不重要了,關(guān)鍵是否真的知道面試官在考察我們什么。

最終就是
new ((new Foo()).getName)();
new有參數(shù)列表(18)->new有參數(shù)列表(18)
先初始化Foo的實(shí)例化對(duì)象,然后將其原型上的getName函數(shù)作為構(gòu)造函數(shù)再次new,所以最終結(jié)果為3



原型

function yideng(){}
const a = {}, b = Object.prototype;
console.log(a.prototype === b);
console.log(Object.getPrototypeOf(a) === b);
console.log(yideng.prototype === Object.getPrototypeOf(yideng));
// 寫出執(zhí)行結(jié)果,并解釋為什么?
// 答案
false true false

//解析:

1)a.prototype === b  =>false
prototype屬性是只有函數(shù)才特有的屬性,當(dāng)你創(chuàng)建一個(gè)函數(shù)時(shí),js會(huì)自動(dòng)為這個(gè)函數(shù)加上prototype屬性,值是一個(gè)空對(duì)象。而實(shí)例對(duì)象是沒(méi)有prototype屬性的。所以a.prototype是undefined,第一個(gè)結(jié)果為false。

2)Object.getPrototypeOf(a) === b =>true
首先要明確對(duì)象和構(gòu)造函數(shù)的關(guān)系,對(duì)象在創(chuàng)建的時(shí)候,其__proto__會(huì)指向其構(gòu)造函數(shù)的prototype屬性
Object實(shí)際上是一個(gè)構(gòu)造函數(shù)(typeof Object的結(jié)果為"function"),使用字面量創(chuàng)建對(duì)象和new Object創(chuàng)建對(duì)象是一樣的,所以a.__proto__也就是Object.prototype,所以O(shè)bject.getPrototypeOf(a)與a.__proto__是一樣的,第二個(gè)結(jié)果為true

3)yideng.prototype === Object.getPrototypeOf(yideng) =>false
關(guān)鍵點(diǎn):f.prototype和Object.getPrototypeOf(f)說(shuō)的不是一回事

①f.prototype 是使用使用 new 創(chuàng)建的 f 實(shí)例的原型:
f.prototype === Object.getPrototypeOf(new f()); // true

②Object.getPrototypeOf(f)是 f 函數(shù)的原型:
Object.getPrototypeOf(f) === Function.prototype; //true

所以答案是 false

//知識(shí)點(diǎn)
__proto__(隱式原型)與prototype(顯式原型)
1)是什么?
①顯式原型 explicit prototype property:
每一個(gè)函數(shù)在創(chuàng)建之后都會(huì)擁有一個(gè)名為prototype的屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。(需要注意的是,通過(guò)Function.prototype.bind方法構(gòu)造出來(lái)的函數(shù)是個(gè)例外,它沒(méi)有prototype屬性)
②隱式原型 implicit prototype link:
JavaScript中任意對(duì)象都有一個(gè)內(nèi)置屬性[[prototype]],在ES5之前沒(méi)有標(biāo)準(zhǔn)的方法訪問(wèn)這個(gè)內(nèi)置屬性,但是大多數(shù)瀏覽器都支持通過(guò)__proto__來(lái)訪問(wèn)。ES5中有了對(duì)于這個(gè)內(nèi)置屬性標(biāo)準(zhǔn)的Get方法Object.getPrototypeOf()。(注意:Object.prototype 這個(gè)對(duì)象是個(gè)例外,它的__proto__值為null)
③二者的關(guān)系:
隱式原型指向創(chuàng)建這個(gè)對(duì)象的函數(shù)(constructor)的prototype

2)作用是什么?
①顯式原型的作用:用來(lái)實(shí)現(xiàn)基于原型的繼承與屬性的共享。
②隱式原型的作用:構(gòu)成原型鏈,同樣用于實(shí)現(xiàn)基于原型的繼承。舉個(gè)例子,當(dāng)我們?cè)L問(wèn)obj這個(gè)對(duì)象中的x屬性時(shí),如果在obj中找不到,那么就會(huì)沿著__proto__依次查找。

3)__proto__的指向:
__proto__的指向到底如何判斷呢?根據(jù)ECMA定義 'to the value of its constructor’s "prototype" ' ----指向創(chuàng)建這個(gè)對(duì)象的函數(shù)的顯式原型。
所以關(guān)鍵的點(diǎn)在于找到創(chuàng)建這個(gè)對(duì)象的構(gòu)造函數(shù),接下來(lái)就來(lái)看一下JS中對(duì)象被創(chuàng)建的方式,一眼看過(guò)去似乎有三種方式:(1)對(duì)象字面量的方式 (2)new 的方式 (3)ES5中的Object.create()。
但是本質(zhì)上只有一種方式,也就是通過(guò)new來(lái)創(chuàng)建。為什么這么說(shuō)呢,首先字面量的方式是一種為了開發(fā)人員更方便創(chuàng)建對(duì)象的一個(gè)語(yǔ)法糖,本質(zhì)就是 var o = new Object(); o.xx = xx;o.yy=yy;



加載頁(yè)面和渲染過(guò)程

回答問(wèn)題的時(shí)候,關(guān)鍵要抓住核心的要點(diǎn),把要點(diǎn)說(shuō)全面,然后再稍微加一些解析,要簡(jiǎn)明扼要,思路清晰,不能拖沓。

面試題目:瀏覽器從加載到渲染頁(yè)面的過(guò)程

    首先回答加載的流程,回答要點(diǎn)
        瀏覽器根據(jù) DNS 服務(wù)器得到域名的 IP 地址
        向這個(gè) IP 的機(jī)器發(fā)送 HTTP 請(qǐng)求
        服務(wù)器收到、處理并返回 HTTP 請(qǐng)求
        瀏覽器得到返回內(nèi)容
    稍加分析
        例如在瀏覽器輸入https//yidengxuetang.com的時(shí)候,首先經(jīng)過(guò) DNS 解析,yidengxuetang.com對(duì)應(yīng)的 IP 是101.200.185.250,然后瀏覽器向該 IP 發(fā)送 HTTP 請(qǐng)求。
        server 端接收到 HTTP 請(qǐng)求,然后經(jīng)過(guò)計(jì)算(向不同的用戶推送不同的內(nèi)容),返回 HTTP 請(qǐng)求,返回的內(nèi)容其實(shí)就是一堆 HMTL 格式的字符串,因?yàn)橹挥?HTML 格式瀏覽器才能正確解析,這是 W3C 標(biāo)準(zhǔn)的要求。
    接下來(lái)就是渲染過(guò)程了,回答要點(diǎn)
        根據(jù) HTML 結(jié)構(gòu)生成 DOM 樹
        根據(jù) CSS 生成 CSSOM
        將 DOM 和 CSSOM 整合形成 RenderTree
        根據(jù) RenderTree 開始渲染和展示
        遇到<script>時(shí),會(huì)執(zhí)行并阻塞渲染
    加以分析
        瀏覽器已經(jīng)拿到了 server 端返回的 HTML 內(nèi)容,開始解析并渲染。最初拿到的內(nèi)容就是一堆字符串,必須先結(jié)構(gòu)化成計(jì)算機(jī)擅長(zhǎng)處理的基本數(shù)據(jù)結(jié)構(gòu),因此要把 HTML 字符串轉(zhuǎn)化成 DOM 樹 —— 樹是最基本的數(shù)據(jù)結(jié)構(gòu)之一。
        解析過(guò)程中,如果遇到<link href="...">和<script src="...">這種外鏈加載 CSS 和 JS 的標(biāo)簽,瀏覽器會(huì)異步下載,下載過(guò)程和上文中下載 HTML 的流程一樣。只不過(guò),這里下載下來(lái)的字符串是 CSS 或者 JS 格式的。
        瀏覽器將 CSS 生成 CSSOM,再將 DOM 和 CSSOM 整合成 RenderTree ,然后針對(duì) RenderTree 即可進(jìn)行渲染了。大家可以想一下,有 DOM 結(jié)構(gòu)、有樣式,此時(shí)就能滿足渲染的條件了。另外,這里也可以解釋一個(gè)問(wèn)題 —— 為何要將 CSS 放在 HTML 頭部?—— 這樣會(huì)讓瀏覽器盡早拿到 CSS 盡早生成 CSSOM,然后在解析 HTML 之后可一次性生成最終的 RenderTree,渲染一次即可。如果 CSS 放在 HTML 底部,會(huì)出現(xiàn)渲染卡頓的情況,影響性能和體驗(yàn)。
        最后,渲染過(guò)程中,如果遇到<script>就停止渲染,執(zhí)行 JS 代碼。因?yàn)闉g覽器渲染和 JS 執(zhí)行共用一個(gè)線程,而且這里必須是單線程操作,多線程會(huì)產(chǎn)生渲染 DOM 沖突。待<script>內(nèi)容執(zhí)行完之后,瀏覽器繼續(xù)渲染。最后再思考一個(gè)問(wèn)題 —— 為何要將 JS 放在 HTML 底部?—— JS 放在底部可以保證讓瀏覽器優(yōu)先渲染完現(xiàn)有的 HTML 內(nèi)容,讓用戶先看到內(nèi)容,體驗(yàn)好。另外,JS 執(zhí)行如果涉及 DOM 操作,得等待 DOM 解析完成才行,JS 放在底部執(zhí)行時(shí),HTML 肯定都解析成了 DOM 結(jié)構(gòu)。JS 如果放在 HTML 頂部,JS 執(zhí)行的時(shí)候 HTML 還沒(méi)來(lái)得及轉(zhuǎn)換為 DOM 結(jié)構(gòu),可能會(huì)報(bào)錯(cuò)。

性能優(yōu)化

性能優(yōu)化的題目也是面試??嫉模@類題目有很大的擴(kuò)展性,能夠擴(kuò)展出來(lái)很多小細(xì)節(jié),而且對(duì)個(gè)人的技術(shù)視野和業(yè)務(wù)能力有很大的挑戰(zhàn)。

    優(yōu)化原則是以更好的用戶體驗(yàn)為標(biāo)準(zhǔn)的,目標(biāo)就是
        多使用內(nèi)存、緩存或者其他方法
        減少 CPU 和GPU 計(jì)算,更快展現(xiàn)

    優(yōu)化方向

        減少頁(yè)面體積,提升網(wǎng)絡(luò)加載
            靜態(tài)資源的壓縮合并(JS 代碼壓縮合并、CSS 代碼壓縮合并、雪碧圖)
            靜態(tài)資源緩存(資源名稱加 MD5 戳)
            使用 CDN 讓資源加載更快

        優(yōu)化頁(yè)面渲染

            CSS 放前面,JS 放后面

            懶加載(圖片懶加載、下拉加載更多)

            減少DOM 查詢,對(duì) DOM 查詢做緩存

            減少DOM 操作,多個(gè)操作盡量合并在一起執(zhí)行(DocumentFragment)

            事件節(jié)流

            盡早執(zhí)行操作(DOMContentLoaded)

                window.addEventListener('load', function () {
                    // 頁(yè)面的全部資源加載完才會(huì)執(zhí)行,包括圖片、視頻等
                })
                document.addEventListener('DOMContentLoaded', function () {
                    // DOM 渲染完即可執(zhí)行,此時(shí)圖片、視頻還可能沒(méi)有加載完
                })


            使用 SSR 后端渲染,數(shù)據(jù)直接輸出到 HTML 中,減少瀏覽器使用 JS 模板渲染頁(yè)面 HTML 的時(shí)間

安全
XSS

面試題:什么是xss攻擊

XSS 全稱是 Cross Site Scripting(即跨站腳本),為了和 CSS 區(qū)分,故叫它XSS。XSS 攻擊是指瀏覽器中執(zhí)行惡意腳本(無(wú)論是跨域還是同域),從而拿到用戶的信息并進(jìn)行操作。

這些操作一般可以完成下面這些事情:

    竊取Cookie。
    監(jiān)聽用戶行為,比如輸入賬號(hào)密碼后直接發(fā)送到黑客服務(wù)器。
    修改 DOM 偽造登錄表單。
    在頁(yè)面中生成浮窗廣告。

通常情況,XSS 攻擊的實(shí)現(xiàn)有三種方式——存儲(chǔ)型、反射型和文檔型。原理都比較簡(jiǎn)單,我們來(lái)了解下

    存儲(chǔ)型

存儲(chǔ)型,顧名思義就是將惡意腳本存儲(chǔ)了起來(lái),確實(shí),存儲(chǔ)型的 XSS 將腳本存儲(chǔ)到了服務(wù)端的數(shù)據(jù)庫(kù),然后在客戶端執(zhí)行這些腳本,從而達(dá)到攻擊的效果。

常見的場(chǎng)景是留言評(píng)論區(qū)提交一段腳本代碼,如果前后端沒(méi)有做好轉(zhuǎn)義的工作,那評(píng)論內(nèi)容存到了數(shù)據(jù)庫(kù),在頁(yè)面渲染過(guò)程中直接執(zhí)行, 相當(dāng)于執(zhí)行一段未知邏輯的 JS 代碼,是非??植赖摹_@就是存儲(chǔ)型的 XSS 攻擊。

    反射型

反射型XSS指的是惡意腳本作為網(wǎng)絡(luò)請(qǐng)求的一部分。

比如我輸入:

http://yideng.com?q=<script>alert("你完蛋了")</script>
復(fù)制代碼



這楊,在服務(wù)器端會(huì)拿到q參數(shù),然后將內(nèi)容返回給瀏覽器端,瀏覽器將這些內(nèi)容作為HTML的一部分解析,發(fā)現(xiàn)是一個(gè)腳本,直接執(zhí)行,這樣就被攻擊了。

之所以叫它反射型, 是因?yàn)閻阂饽_本是通過(guò)作為網(wǎng)絡(luò)請(qǐng)求的參數(shù),經(jīng)過(guò)服務(wù)器,然后再反射到HTML文檔中,執(zhí)行解析。和存儲(chǔ)型不一樣的是,服務(wù)器并不會(huì)存儲(chǔ)這些惡意腳本。

    文檔型

文檔型的 XSS 攻擊并不會(huì)經(jīng)過(guò)服務(wù)端,而是作為中間人的角色,在數(shù)據(jù)傳輸過(guò)程劫持到網(wǎng)絡(luò)數(shù)據(jù)包,然后修改里面的 html 文檔!

這樣的劫持方式包括WIFI路由器劫持或者本地惡意軟件等。
防范措施

明白了三種XSS攻擊的原理,我們能發(fā)現(xiàn)一個(gè)共同點(diǎn): 都是讓惡意腳本直接能在瀏覽器中執(zhí)行。

那么要防范它,就是要避免這些腳本代碼的執(zhí)行。

為了完成這一點(diǎn),必須做到

千萬(wàn)不要相信任何用戶的輸入!

無(wú)論是在前端和服務(wù)端,都要對(duì)用戶的輸入進(jìn)行轉(zhuǎn)碼或者過(guò)濾。

如:

<script>alert('你完蛋了')</script>
復(fù)制代碼



轉(zhuǎn)碼后變?yōu)?

&lt;script&gt;alert(&#39;你完蛋了&#39;)&lt;/script&gt;
復(fù)制代碼


這樣的代碼在 html 解析的過(guò)程中是無(wú)法執(zhí)行的。

當(dāng)然也可以利用關(guān)鍵詞過(guò)濾的方式,將 script 標(biāo)簽給刪除。那么現(xiàn)在的內(nèi)容只剩下:
利用 HttpOnly

很多 XSS 攻擊腳本都是用來(lái)竊取Cookie, 而設(shè)置 Cookie 的 HttpOnly 屬性后,JavaScript 便無(wú)法讀取 Cookie 的值。這樣也能很好的防范 XSS 攻擊。
CSRF

面試題:說(shuō)一下CSRF攻擊
什么是CSRF攻擊?

CSRF(Cross-site request forgery), 即跨站請(qǐng)求偽造,指的是黑客誘導(dǎo)用戶點(diǎn)擊鏈接,打開黑客的網(wǎng)站,然后黑客利用用戶目前的登錄狀態(tài)發(fā)起跨站請(qǐng)求。

舉個(gè)例子, 你在某個(gè)論壇點(diǎn)擊了黑客精心挑選的小姐姐圖片,你點(diǎn)擊后,進(jìn)入了一個(gè)新的頁(yè)面。

那么恭喜你,被攻擊了:)

你可能會(huì)比較好奇,怎么突然就被攻擊了呢?接下來(lái)我們就來(lái)拆解一下當(dāng)你點(diǎn)擊了鏈接之后,黑客在背后做了哪些事情。

可能會(huì)做三樣事情。列舉如下:
1. 自動(dòng)發(fā) GET 請(qǐng)求

黑客網(wǎng)頁(yè)里面可能有一段這樣的代碼:

<img src="https://xxx.com/info?user=hhh&count=100">



進(jìn)入頁(yè)面后自動(dòng)發(fā)送 get 請(qǐng)求,值得注意的是,這個(gè)請(qǐng)求會(huì)自動(dòng)帶上關(guān)于 xxx.com 的 cookie 信息(這里是假定你已經(jīng)在 xxx.com 中登錄過(guò))。

假如服務(wù)器端沒(méi)有相應(yīng)的驗(yàn)證機(jī)制,它可能認(rèn)為發(fā)請(qǐng)求的是一個(gè)正常的用戶,因?yàn)閿y帶了相應(yīng)的 cookie,然后進(jìn)行相應(yīng)的各種操作,可以是轉(zhuǎn)賬匯款以及其他的惡意操作。
2. 自動(dòng)發(fā) POST 請(qǐng)求

黑客可能自己填了一個(gè)表單,寫了一段自動(dòng)提交的腳本。

<form id='hacker-form' action="https://xxx.com/info" method="POST">
  <input type="hidden" name="user" value="hhh" />
  <input type="hidden" name="count" value="100" />
</form>
<script>document.getElementById('hacker-form').submit();</script>
復(fù)制代碼



同樣也會(huì)攜帶相應(yīng)的用戶 cookie 信息,讓服務(wù)器誤以為是一個(gè)正常的用戶在操作,讓各種惡意的操作變?yōu)榭赡堋?br>3. 誘導(dǎo)點(diǎn)擊發(fā)送 GET 請(qǐng)求

在黑客的網(wǎng)站上,可能會(huì)放上一個(gè)鏈接,驅(qū)使你來(lái)點(diǎn)擊:

<a href="https://xxx/info?user=hhh&count=100" taget="_blank">點(diǎn)擊進(jìn)入修仙世界</a>



點(diǎn)擊后,自動(dòng)發(fā)送 get 請(qǐng)求,接下來(lái)和自動(dòng)發(fā) GET 請(qǐng)求部分同理。

這就是CSRF攻擊的原理。和XSS攻擊對(duì)比,CSRF 攻擊并不需要將惡意代碼注入用戶當(dāng)前頁(yè)面的html文檔中,而是跳轉(zhuǎn)到新的頁(yè)面,利用服務(wù)器的驗(yàn)證漏洞和用戶之前的登錄狀態(tài)來(lái)模擬用戶進(jìn)行操作。
防范措施
1. 利用Cookie的SameSite屬性

CSRF攻擊中重要的一環(huán)就是自動(dòng)發(fā)送目標(biāo)站點(diǎn)下的 Cookie,然后就是這一份 Cookie 模擬了用戶的身份。因此在Cookie上面下文章是防范的不二之選。

恰好,在 Cookie 當(dāng)中有一個(gè)關(guān)鍵的字段,可以對(duì)請(qǐng)求中 Cookie 的攜帶作一些限制,這個(gè)字段就是SameSite。

SameSite可以設(shè)置為三個(gè)值,Strict、Lax和None。

a. 在Strict模式下,瀏覽器完全禁止第三方請(qǐng)求攜帶Cookie。比如請(qǐng)求sanyuan.com網(wǎng)站只能在sanyuan.com域名當(dāng)中請(qǐng)求才能攜帶 Cookie,在其他網(wǎng)站請(qǐng)求都不能。

b. 在Lax模式,就寬松一點(diǎn)了,但是只能在 get 方法提交表單況或者a 標(biāo)簽發(fā)送 get 請(qǐng)求的情況下可以攜帶 Cookie,其他情況均不能。

c. 在None模式下,也就是默認(rèn)模式,請(qǐng)求會(huì)自動(dòng)攜帶上 Cookie。
2. 驗(yàn)證來(lái)源站點(diǎn)

這就需要要用到請(qǐng)求頭中的兩個(gè)字段: Origin和Referer。

其中,Origin只包含域名信息,而Referer包含了具體的 URL 路徑。

當(dāng)然,這兩者都是可以偽造的,通過(guò) Ajax 中自定義請(qǐng)求頭即可,安全性略差。
3. CSRF Token

它的原理是怎樣的呢?

首先,瀏覽器向服務(wù)器發(fā)送請(qǐng)求時(shí),服務(wù)器生成一個(gè)字符串,將其植入到返回的頁(yè)面中。

然后瀏覽器如果要發(fā)送請(qǐng)求,就必須帶上這個(gè)字符串,然后服務(wù)器來(lái)驗(yàn)證是否合法,如果不合法則不予響應(yīng)。這個(gè)字符串也就是CSRF Token,通常第三方站點(diǎn)無(wú)法拿到這個(gè) token, 因此也就是被服務(wù)器給拒絕。

防范措施: 利用 Cookie 的 SameSite 屬性、驗(yàn)證來(lái)源站點(diǎn)和CSRF Token。
算法題

/*
    給定一個(gè)只包括 '(',')','{','}','[',']' 的字符串,判斷字符串是否有效
  有效字符串需滿?:
          1. 左括號(hào)必須?相同類型的右括號(hào)閉合。
      2. 左括號(hào)必須以正確的順序閉合。
  注意空字符串可被認(rèn)為是有效字符串。
  示例1:
      輸?: "()"
      輸出: true
  示例2:
      輸?: "()[]{}"
      輸出: true
  示例 3:
      輸?: "(]"
      輸出: false
  示例 4:
      輸?: "([)]"
      輸出: false
  示例 5:
      輸?: "{[]}"
      輸出: true
*/

// 思路
出棧、入棧的思想
1)首先,我們通過(guò)上邊的例子可以分析出什么樣子括號(hào)匹配是復(fù)合物條件的,兩種情況。
    ①第一種(非嵌套情況):{} [] ;
    ②第二種(嵌套情況):{ [ ( ) ] } 。
除去這兩種情況都不是符合條件的。
2)然后,我們將這些括號(hào)自右向左看做棧結(jié)構(gòu),右側(cè)是棧頂,左側(cè)是棧尾。
3)如果編譯器中的括號(hào)是左括號(hào),我們就入棧(左括號(hào)不用檢查匹配);如果是右括號(hào),就取出棧頂元素檢查是否匹配。
4)如果匹配,就出棧。否則,就返回 false;

// 代碼實(shí)現(xiàn)
var isValid = function(s){
  let stack = [];
  var obj = {
     "[": "]",
     "{": "}",
     "(": ")",
  };
  // 取出字符串中的括號(hào)
  for (var i = 0; i < s.length;i++){
    if(s[i] === "[" || s[i] === "{" || s[i] === "("){
      // 如果是左括號(hào),就進(jìn)棧
      stack.push(s[i]);
    }else{
           var key = stack.pop();
      // 如果棧頂元素不相同,就返回false
      if(obj[key] !== s[i]){
        return false;
      }
    }
  }
  return stack.length ===  0
}



因此這就需要一個(gè)系統(tǒng)的訓(xùn)練,進(jìn)行專項(xiàng)突破,按分類進(jìn)行學(xué)習(xí)

比如按照經(jīng)典的數(shù)據(jù)結(jié)構(gòu)進(jìn)行分類,刷題

    常見的數(shù)據(jù)結(jié)構(gòu)
        棧、隊(duì)列、鏈表
        集合、字典、散列表
    常見算法
        遞歸
        排序
        枚舉
    算法復(fù)雜度分析
    算法思維
        分治
        貪心
        動(dòng)態(tài)規(guī)劃
    高級(jí)數(shù)據(jù)結(jié)構(gòu)
        樹、圖
        深度優(yōu)先和廣度優(yōu)先搜索

框架相關(guān)
Vue響應(yīng)式原理介紹一下,Object.defineProperty 的缺陷,模板編譯的過(guò)程,將模板解析為 AST,優(yōu)化AST,將AST轉(zhuǎn)換為render函數(shù) 等等。
HOC是什么?相比mixins有什么優(yōu)點(diǎn)

很多人看到高階組件(HOC)這個(gè)概念就被嚇到了,認(rèn)為這東西很難,其實(shí)這東西概念真的很簡(jiǎn)單,我們先來(lái)看一個(gè)例子。

function add(a, b) {
    return a + b
}
//現(xiàn)在如果我想給這個(gè) add 函數(shù)添加一個(gè)輸出結(jié)果的功能,那么你可能會(huì)考慮我直接使用 console.log 不就實(shí)現(xiàn)了么。說(shuō)的沒(méi)錯(cuò),但是如果我們想做的更加優(yōu)雅并且容易復(fù)用和擴(kuò)展,我們可以這樣去做:
function withLog (fn) {
    function wrapper(a, b) {
        const result = fn(a, b)
        console.log(result)
        return result
    }
    return wrapper
}
const withLogAdd = withLog(add)
withLogAdd(1, 2)
//其實(shí)這個(gè)做法在函數(shù)式編程里稱之為高階函數(shù),大家都知道 React 的思想中是存在函數(shù)式編程的,高階組件和高階函數(shù)就是同一個(gè)東西。我們實(shí)現(xiàn)一個(gè)函數(shù),傳入一個(gè)組件,然后在函數(shù)內(nèi)部再實(shí)現(xiàn)一個(gè)函數(shù)去擴(kuò)展傳入的組件,最后返回一個(gè)新的組件,這就是高階組件的概念,作用就是為了更好的復(fù)用代碼。
// 其實(shí) HOC 和 Vue 中的 mixins 作用是一致的,并且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式創(chuàng)建組件以后,mixins 的方式就不能使用了,并且其實(shí) mixins 也是存在一些問(wèn)題的,比如:
隱含了一些依賴,比如我在組件中寫了某個(gè) state 并且在 mixin 中使用了,就這存在了一個(gè)依賴關(guān)系。萬(wàn)一下次別人要移除它,就得去 mixin 中查找依賴
多個(gè) mixin 中可能存在相同命名的函數(shù),同時(shí)代碼組件中也不能出現(xiàn)相同命名的函數(shù),否則就是重寫了,其實(shí)我一直覺(jué)得命名真的是一件麻煩事。。
雪球效應(yīng),雖然我一個(gè)組件還是使用著同一個(gè) mixin,但是一個(gè) mixin 會(huì)被多個(gè)組件使用,可能會(huì)存在需求使得 mixin 修改原本的函數(shù)或者新增更多的函數(shù),這樣可能就會(huì)產(chǎn)生一個(gè)維護(hù)成本

HOC 解決了這些問(wèn)題,并且它們達(dá)成的效果也是一致的,同時(shí)也更加的政治正確(畢竟更加函數(shù)式了)。



聊到具體的項(xiàng)目

一般在面試中,比如一面、二面基本會(huì)問(wèn)到具體做過(guò)的項(xiàng)目,然后就是追問(wèn)項(xiàng)目的細(xì)節(jié)。一般可能會(huì)出現(xiàn)這些問(wèn)題

    發(fā)現(xiàn)你簡(jiǎn)歷的一個(gè)項(xiàng)目,直接讓你介紹下這個(gè)項(xiàng)目
    讓你回憶下你做過(guò)的項(xiàng)目中,最值得分享(最大型/最困難/最能體現(xiàn)技術(shù)能力/最難忘)的
    如果讓你設(shè)計(jì) xx 系統(tǒng)/項(xiàng)目,你會(huì)怎么著手干

這類跟項(xiàng)目相關(guān)的綜合性問(wèn)題,既能體現(xiàn)候選人的技術(shù)水平、業(yè)務(wù)水平和架構(gòu)能力,也能夠辨別候選人是不是真的做過(guò)項(xiàng)目,還能夠發(fā)現(xiàn)候選人的一些軟技能。
如何進(jìn)行應(yīng)對(duì)

    首先簡(jiǎn)歷中的項(xiàng)目,肯定是你精挑細(xì)選的,不能隨便選幾個(gè),要做好充分的準(zhǔn)備,簡(jiǎn)歷中的項(xiàng)目,既要體現(xiàn)技術(shù)難度,又要想好細(xì)節(jié)。一般介紹一個(gè)項(xiàng)目可以按幾步來(lái)
    介紹項(xiàng)目背景
        這個(gè)項(xiàng)目為什么做,當(dāng)初大的環(huán)境背景是什么?還是為了解決一個(gè)什么問(wèn)題而設(shè)立的項(xiàng)目?背景是很重要的,如果不了解背景,一上來(lái)就聽一個(gè)結(jié)論性的項(xiàng)目,面試官可能對(duì)于項(xiàng)目的技術(shù)選型、技術(shù)難度會(huì)有理解偏差,甚至懷疑是否真的有過(guò)這樣的項(xiàng)目。
        比如一上來(lái)就說(shuō):我們的項(xiàng)目采用了「backbone」來(lái)做框架,然后。。。而「backbone」已經(jīng)是三四年前比較新鮮的技術(shù),現(xiàn)在會(huì)有更好的選擇方案,如果不介紹項(xiàng)目的時(shí)間背景,面試官肯定一臉懵逼。
    承擔(dān)角色
        項(xiàng)目涉及的人員角色有哪些,自己在其中扮演的角色是什么?
        這里候選往往人會(huì)自己給自己挖坑,比如把自己在項(xiàng)目中起到的作用夸大等。一般來(lái)說(shuō),面試官細(xì)節(jié)追問(wèn)的時(shí)候,如果候選人能夠把細(xì)節(jié)或者技術(shù)方案等講明白、講清楚,不管他是真的做過(guò)還是跟別人做過(guò),或者自己認(rèn)真思考過(guò),都能體現(xiàn)候選人的技術(shù)水平和技術(shù)視野。前提還是在你能夠兜得住的可控范圍之內(nèi)做適當(dāng)?shù)摹该阑埂?br>    最終的結(jié)果和收益
        項(xiàng)目介紹過(guò)程中,應(yīng)該介紹項(xiàng)目最終的結(jié)果和收益,比如項(xiàng)目最后經(jīng)過(guò)多久的開發(fā)上線了,上線后的數(shù)據(jù)是怎樣的,是否達(dá)到預(yù)期,還是帶來(lái)了新的問(wèn)題,遇見了問(wèn)題自己后續(xù)又是怎樣補(bǔ)救的。
    非常重要的:項(xiàng)目的總結(jié)和反思
        有總結(jié)和反思,才會(huì)有進(jìn)步。 項(xiàng)目做完了往往會(huì)有一些心得和體會(huì),這時(shí)候應(yīng)該跟面試官說(shuō)出來(lái)。在梳理項(xiàng)目的總結(jié)和反思時(shí),可以按照下面的列表來(lái)梳理:
            收獲有哪些?
            是否有做得不足的地方,怎么改進(jìn)?
            是否具有可遷移性?
        比如,之前詳細(xì)介紹了某個(gè)項(xiàng)目,這個(gè)項(xiàng)目當(dāng)時(shí)看來(lái)沒(méi)有什么問(wèn)題,但是現(xiàn)在有更好的解決方案了,候選人就應(yīng)該在這里提出來(lái):現(xiàn)在看來(lái),這個(gè)項(xiàng)目還有 xx 的問(wèn)題,我可以通過(guò) xx 的方式來(lái)解決。
        再比如:做這個(gè)項(xiàng)目的時(shí)候,你做得比較出彩的地方,可以遷移到其他項(xiàng)目中直接使用,小到代碼片段,大到解決方案,總會(huì)有你值得總結(jié)和梳理的地方。
        介紹完項(xiàng)目總結(jié)這部分,也可以引導(dǎo)面試官往自己擅長(zhǎng)的領(lǐng)域思考。比如上面提到項(xiàng)目中的問(wèn)題,可以往你擅長(zhǎng)的方面引導(dǎo),即使面試官?zèng)]有問(wèn)到,你也介紹到了。

按照上面的四段體介紹項(xiàng)目,會(huì)讓面試官感覺(jué)候選人有清晰的思路,對(duì)整個(gè)項(xiàng)目也有理解和想法,還能夠總結(jié)反思項(xiàng)目的收益和問(wèn)題,可謂「一箭三雕」。
沒(méi)有做過(guò)大型項(xiàng)目怎么辦

對(duì)于剛剛找工作的應(yīng)屆生,或者面試官讓你進(jìn)行一個(gè)大型項(xiàng)目的設(shè)計(jì),候選人可能沒(méi)有類似的經(jīng)驗(yàn)。這時(shí)候不要用「我不會(huì)、沒(méi)做過(guò)」一句話就帶過(guò)。

如果是實(shí)在沒(méi)有項(xiàng)目可以說(shuō),那么可以提自己日常做的練手項(xiàng)目,或者看到一個(gè)解決方案的文章/書,提到的某個(gè)項(xiàng)目,抒發(fā)下自己的想法。

如果是對(duì)于面試官提出來(lái)需要你設(shè)計(jì)的項(xiàng)目/系統(tǒng),可以按照下面幾步思考:

    有沒(méi)有遇見過(guò)類似的項(xiàng)目
    有沒(méi)有讀過(guò)類似解決方案的文章
    項(xiàng)目能不能拆解,拆解過(guò)程中能不能發(fā)現(xiàn)自己做過(guò)的項(xiàng)目可以用
    項(xiàng)目解決的問(wèn)題是什么,這類問(wèn)題有沒(méi)有更好的解決方案

總之,切記不要一句「不知道、沒(méi)做過(guò)」就放棄,每一次提問(wèn)都是自己表現(xiàn)的機(jī)會(huì)。
項(xiàng)目細(xì)節(jié)和技術(shù)點(diǎn)的追問(wèn)

介紹項(xiàng)目的過(guò)程中,面試官可能會(huì)追問(wèn)技術(shù)細(xì)節(jié),所以我們?cè)跍?zhǔn)備面試的時(shí)候,應(yīng)該盡量把技術(shù)細(xì)節(jié)梳理清楚,技術(shù)細(xì)節(jié)包括:

    技術(shù)選型方案:當(dāng)時(shí)做技術(shù)選型所面臨的狀況
    技術(shù)解決方案:最終確定某種技術(shù)方案的原因,比如:選擇用 Vue 而沒(méi)有用 React 是為什么?
    項(xiàng)目數(shù)據(jù)和收益
    項(xiàng)目中最難的地方
    遇見的坑:如使用某種框架遇見哪些坑

一般來(lái)說(shuō),做技術(shù)選型的時(shí)候需要考慮下面幾個(gè)因素:

    時(shí)代:現(xiàn)在比較火的技術(shù)是什么,為什么火起來(lái),解決了什么問(wèn)題,能否用到我的項(xiàng)目中?
    團(tuán)隊(duì):個(gè)人或者團(tuán)隊(duì)對(duì)某種技術(shù)的熟悉程度是怎樣的,學(xué)習(xí)成本又是怎樣的?
    業(yè)務(wù)需求:需求是怎樣的,能否套用現(xiàn)在的成熟解決方案/庫(kù)來(lái)快速解決?
    維護(hù)成本:一個(gè)解決方案的是否再能夠 cover 住的范圍之內(nèi)?

在項(xiàng)目中遇見的數(shù)據(jù)和收益應(yīng)該做好跟蹤,保證數(shù)據(jù)的真實(shí)性和可信性。另外,遇見的坑可能是面試官問(wèn)得比較多的,尤其現(xiàn)在比較火的一些技術(shù)(Vue、React、webpack),一般團(tuán)隊(duì)都在使用,所以一定要提前準(zhǔn)備下
軟技能問(wèn)題回答

    韌性:抗壓能力,在一定項(xiàng)目壓力下能夠迎難而上,比如勇于主動(dòng)承擔(dān)和解決技術(shù)難題
    責(zé)任心:對(duì)于自己做過(guò)的項(xiàng)目,能夠出現(xiàn) bug 之類主動(dòng)解決
    持續(xù)學(xué)習(xí)能力:IT 行業(yè)是個(gè)需要不斷充電的行業(yè),尤其 Web 前端這些年一直在巨變,所以持續(xù)學(xué)習(xí)能力很重要
    團(tuán)隊(duì)合作能力:做項(xiàng)目不能個(gè)人英雄主義,應(yīng)該融入團(tuán)隊(duì),跟團(tuán)隊(duì)一起打仗
    交流溝通能力:經(jīng)常會(huì)遇見溝通需求和交互設(shè)計(jì)的工作,應(yīng)該樂(lè)于溝通分享

回想下你遇見過(guò)最難打交道的同事,你是如何跟他溝通的

一般來(lái)說(shuō),工作中總會(huì)遇見一兩個(gè)自己不喜歡的人,這種情況應(yīng)該盡量避免沖突,從自己做起慢慢讓對(duì)方感覺(jué)到自己的合作精神。

所以,遇見難打交道的同事,不要急于上報(bào)領(lǐng)導(dǎo),應(yīng)該自己主動(dòng)多做一些事情,比如規(guī)劃好工作安排,讓他選擇自己做的事情,有了結(jié)論記得發(fā)郵件確認(rèn)下來(lái),這樣你們的領(lǐng)導(dǎo)和其他成員都會(huì)了解到工作的安排,在鞭笞對(duì)方的同時(shí),也做到了職責(zé)明確。在項(xiàng)目當(dāng)中,多主動(dòng)檢查項(xiàng)目進(jìn)展,提前發(fā)現(xiàn)逾期的問(wèn)題。

重點(diǎn)是突出:自己主動(dòng)溝通解決問(wèn)題的意識(shí),而不是遇見問(wèn)題就找領(lǐng)導(dǎo)。
當(dāng)你被分配一個(gè)幾乎不可能完成的任務(wù)時(shí),你會(huì)怎么做

這種情況下,一般通過(guò)下面方式來(lái)解決:

    自己先查找資料,尋找解決方案,評(píng)估自己需要怎樣的資源來(lái)完成,需要多長(zhǎng)時(shí)間
    能不能借助周圍同事來(lái)解決問(wèn)題
    拿著分析結(jié)果跟上級(jí)反饋,尋求幫助或者資源

突出的軟技能:分析和解決問(wèn)題,溝通尋求幫助。
業(yè)余時(shí)間都做什么?除了寫碼之外還有什么愛(ài)好

這類問(wèn)題也是面試官的高頻問(wèn)題,「一個(gè)人的業(yè)余時(shí)間決定了他的未來(lái)」,如果回答周末都在追劇打游戲之類的,未免顯得太不上進(jìn)。

一般來(lái)說(shuō),推薦下面的回答:

    周末一般會(huì)有三種狀態(tài):

        和朋友一起去做做運(yùn)動(dòng),也會(huì)聚會(huì)聊天,探討下新技術(shù)之類的;
        也會(huì)看一些書籍充充電,比如我最近看的 xx,有什么的想法;
        有時(shí)候會(huì)悶在家用最近比較火的技術(shù)做個(gè)小項(xiàng)目或者實(shí)現(xiàn)個(gè)小功能之類的。

這樣的回答,既能表現(xiàn)自己陽(yáng)光善于社交溝通的一面,又能表現(xiàn)自己的上進(jìn)心。
面試注意事項(xiàng)
提問(wèn)環(huán)節(jié)

面試是一個(gè)雙向選擇的事情,所以面試后一般會(huì)有提問(wèn)環(huán)節(jié)。在提問(wèn)環(huán)節(jié),候選人最好不要什么都不問(wèn),更不要只問(wèn)薪水待遇、是否加班之類的問(wèn)題。

其實(shí)這個(gè)時(shí)候可以反問(wèn)面試官了解團(tuán)隊(duì)情況、團(tuán)隊(duì)做的業(yè)務(wù)、本職位具體做的工作、工作的規(guī)劃,甚至一些數(shù)據(jù)(可能有些問(wèn)題不會(huì)直面回答)。

還可以問(wèn)一些關(guān)于公司培訓(xùn)機(jī)會(huì)和晉升機(jī)會(huì)之類的問(wèn)題。如果是一些高端職位,則可以問(wèn)一下:自己的 leader 想把這個(gè)職位安排給什么樣的人,希望多久的時(shí)間內(nèi)可以達(dá)到怎樣的水平。
面試后的總結(jié)和思考

面試完了多總結(jié)自己哪里做得不好,哪里做得好,都記錄下來(lái),后續(xù)揚(yáng)長(zhǎng)避短



作者:Vam的金豆之路

主要領(lǐng)域:前端開發(fā)

我的微信:maomin9761

微信公眾號(hào):前端歷劫之路