常見面試題總結(jié)(一)

常見面試題總結(jié)(一)

什么是原型秦踪?

  • 原型分為兩種:構(gòu)造函數(shù)原型枪眉,稱為prototype狸驳;實(shí)例(對(duì)象)原型稱為proto

原型之間的關(guān)系?

  • 由構(gòu)造函數(shù)創(chuàng)建出來的實(shí)例對(duì)象上役耕,proto屬性指向了構(gòu)造函數(shù)的prototype屬性

構(gòu)造函數(shù)和構(gòu)造器是什么采转?

  • js中,默認(rèn)每一個(gè)(正常定義的)函數(shù)瞬痘,都是構(gòu)造器故慈,也稱為類,那么構(gòu)造函數(shù)上的原型鏈上有? Function() { [native code] }這個(gè)屬性框全,所以可以通過new 方法創(chuàng)建實(shí)例察绷,構(gòu)造函數(shù)原型prototype上有一個(gè)constructor構(gòu)造器屬性,指向了構(gòu)造函數(shù)自身
  • 能夠稱為構(gòu)造器的關(guān)鍵在于擁有--->prototype屬性(實(shí)例沒有)
function Fa() {}

Fa.constructor
? Function() { [native code] }
  • 被new出來的實(shí)例津辩,由于不是一個(gè)構(gòu)造器拆撼,所以無法通過new創(chuàng)建新的實(shí)例--->沒有prototype屬性
const son = new Fa()

son.constructor
// ? Fa() {}
const subSon = new son()
// VM5278:1 Uncaught TypeError: son is not a constructor
    at <anonymous>:1:16
  • 一張圖描述清楚構(gòu)造器想關(guān)
  • image.png
    image.png
  • 有些人可能會(huì)把 __proto__prototype 搞混淆。從翻譯的角度來說喘沿,它們都可以叫原型闸度,但是其實(shí)是完全不同的兩個(gè)東西。

__proto__ 存在于所有的對(duì)象上蚜印,prototype 存在于所有的函數(shù)上筋岛,他倆的關(guān)系就是:函數(shù)的 prototype 是所有使用 new 這個(gè)函數(shù)構(gòu)造的實(shí)例的 __proto__。函數(shù)也是對(duì)象晒哄,所以函數(shù)同時(shí)有 __proto__prototype

作者:余騰靖
鏈接:https://juejin.im/post/5e2ff7dce51d4558021a1a4d
來源:掘金
著作權(quán)歸作者所有肪获。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)寝凌,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

原型鏈?zhǔn)鞘裁矗?/h1>
  • 實(shí)例擁有的實(shí)例原型對(duì)象proto屬性孝赫,指向了構(gòu)造函數(shù)原型對(duì)象prototype屬性较木,而構(gòu)造函數(shù)原型對(duì)象prototype上的proto屬性也指向了父構(gòu)造函數(shù)的原型prototype,以此類推青柄,通過proto建立起來的指向關(guān)系就被稱為原型鏈

原型鏈的作用伐债?

  • 實(shí)例訪問一個(gè)屬性的時(shí)候预侯,先在自身可遍歷屬性上尋找,再到原型鏈上逐層向上尋找峰锁,如果找不到會(huì)返回undefined
  • stu.proto.proto.proto萎馅,直到遇到null,則返回undefined
  • image.png
    image.png
  • 作用:將類的方法定義在原型上虹蒋,通過這種方式糜芳,所有的實(shí)例都可以訪問到這個(gè)方法,并且這個(gè)方法只需要占用一份內(nèi)存魄衅,節(jié)省內(nèi)存峭竣,this 的指向還能正確指向類的實(shí)例。
  • 注意:這種方法定義的屬性晃虫,由于不是自身屬性皆撩,所以無法通過Object.keys()枚舉
function Engineer(workingYears) {
  this.workingYears = workingYears;
}

// 不能使用箭頭函數(shù),箭頭函數(shù)的 this 在聲明的時(shí)候就根據(jù)上下文確定了
Engineer.prototype.built = function() {
  // this 這里就是執(zhí)行函數(shù)調(diào)用者
  console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};

const engineer = new Engineer(5);
// this 會(huì)正確指向?qū)嵗芤?this.workingYears 是 5
engineer.built(); // => 我已經(jīng)工作 5 年了, 我的工作是擰螺絲...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]

  • 優(yōu)點(diǎn):通過這種方式扛吞,所有的實(shí)例都可以訪問到這個(gè)方法,并且這個(gè)方法只需要占用一份內(nèi)存盘榨,節(jié)省內(nèi)存喻粹,this 的指向還能正確指向類的實(shí)例。
  • 不可枚舉示例:
function Engineer(workingYears) {
  this.workingYears = workingYears;
}

// 不能使用箭頭函數(shù)草巡,箭頭函數(shù)的 this 在聲明的時(shí)候就根據(jù)上下文確定了
Engineer.prototype.built = function() {
  // this 這里就是執(zhí)行函數(shù)調(diào)用者
  console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
};

const engineer = new Engineer(5);
// this 會(huì)正確指向?qū)嵗匚兀?this.workingYears 是 5
engineer.built(); // => 我已經(jīng)工作 5 年了, 我的工作是擰螺絲...
console.log(Object.keys(engineer)); // => [ 'workingYears' ]

  • 可枚舉方法示例:
function Engineer(workingYears) {
  this.workingYears = workingYears;
  this.built = function() {
    console.log(`我已經(jīng)工作 ${this.workingYears} 年了, 我的工作是擰螺絲...`);
  };
}

const engineer = new Engineer(5);
console.log(Object.keys(engineer)); // => [ 'workingYears', 'built' ]
  • 常見:Array.prototype.slice,Object.prototype.toString就是定義在了原型上的函數(shù)山憨。

<br />

ES6的class本質(zhì)是什么查乒?

  • 其實(shí),ES6 class 就是構(gòu)造器的語法糖郁竟。
  • ES6 的 class 就是構(gòu)造器玛迄,class 上的方法定義在構(gòu)造器的 prototype 上,因此你也可以理解為什么 class 的方法是不可枚舉的棚亩。

<br />

class extends實(shí)現(xiàn)繼承的本質(zhì)蓖议?

  • 原型繼承+組合繼承
// 原型繼承
function _inheritsLoose(subClass, superClass) {
  subClass.prototype = Object.create(superClass.prototype);
  subClass.prototype.constructor = subClass;
  // 讓子類可以訪問父類上的靜態(tài)屬性,其實(shí)就是定義在構(gòu)造器自身上的屬性
  // 例如父類有 Person.say 屬性讥蟆,子類 Student 通過可以通過 Student.say 訪問
  subClass.__proto__ = superClass;
}

var Shape = function Shape(x, y) {
  this.x = x;
  this.y = y;
};

var Circle = (function(_Shape) {
  _inheritsLoose(Circle, _Shape);

  function Circle(x, y, r) {
    var _this;

    // 組合繼承
    _this = _Shape.call(this, x, y) || this;
    _this.r = r;
    return _this;
  }

  var _proto = Circle.prototype;

  _proto.draw = function draw() {
    console.log(
      '\u753B\u4E2A\u5750\u6807\u4E3A (' +
        this.x +
        ', ' +
        this.y +
        ')\uFF0C\u534A\u5F84\u4E3A ' +
        this.r +
        ' \u7684\u5706'
    );
  };

  return Circle;
})(Shape);

繼承有哪幾種方式勒虾?

(1)借助構(gòu)造函數(shù)+call/apply實(shí)現(xiàn)繼承

// 借助構(gòu)造函數(shù)實(shí)現(xiàn)繼承
function Parent(argument) {
    this.name='parent';
}
Parent.prototype.say=function(){}
function Child(argument) {
    Parent.call(this); // 原理就是call/apply, call和apply改變了父類this中的指向瘸彤,使this指向了子類修然,這樣就可以把父類的屬性掛載到子類里
    this.age=11;
}
var child = new Child();
console.log(child);  // Child {name: "parent", age: 11}

缺點(diǎn)是只能繼承父類實(shí)例上的屬性,無法繼承原型鏈上的屬性。<br />(2)借助原型鏈實(shí)現(xiàn)繼承

// 借助原型鏈實(shí)現(xiàn)繼承
function Parent1(argument) {
    this.name='parent';
    this.age=[1,2,3];
}
function Child1(argument) {
    this.name='child';
}
Child1.prototype=new Parent1(); // 將父類的實(shí)例賦值給子類的原型,這樣子類就繼承了父類
var child11 = new Child1();
console.log(child11)  // Child1 {age: 11 , __proto__: Parent1}
/*******************原型鏈繼承的缺點(diǎn)********************/
var child12=new Child1();
child11.age.push(4); // 往其中一個(gè)實(shí)例的引用屬性添加一個(gè)元素
console.log(child11.age,child12.age) // 會(huì)發(fā)現(xiàn)都是打印出  [1, 2, 3, 4]

缺點(diǎn):當(dāng)父類有引用屬性時(shí)愕宋,由于原型對(duì)象的特點(diǎn)玻靡,多個(gè)實(shí)例對(duì)象的proto都是同一個(gè),而引用屬性在new的時(shí)候不會(huì)開辟新的地址中贝,所以當(dāng)一個(gè)實(shí)例對(duì)象改變了引用屬性的值時(shí)囤捻,另一個(gè)對(duì)象也會(huì)隨之改變。<br />(3)結(jié)合構(gòu)造函數(shù)和原型鏈的方式

// 組合方式雄妥,結(jié)合上面兩種
function Parent3(argument) {
    this.name='parent';
    this.age=[1,2,3]
}
Parent3.prototype.say=function(){}
function Child3(argument) {
    Parent3.call(this); // 結(jié)合構(gòu)造函數(shù)
    this.type='test';
}
Child3.prototype=new Parent3();  // 結(jié)合原型鏈
var child31 = new Child3();
var child32 = new Child3();
console.log(child31,child32);
child31.age.push(4);
console.log(child31.age,child32.age);.//  [1, 2, 3, 4]      [1, 2, 3]

這種方式可以解決前兩種方式的問題最蕾。缺點(diǎn):父類的構(gòu)造函數(shù)會(huì)執(zhí)行兩次。<br />優(yōu)化:把上面的

Child3.prototype=new Parent3();

換成

Child3.prototype=Parent3.prototype;

以上方法還是存在不足老厌,因?yàn)橹灰峭ㄟ^原型鏈繼承來的對(duì)象瘟则,它的constructor打印出來都是父類Parent3,即無法確認(rèn)child31實(shí)例是由父類創(chuàng)造的還是由子類創(chuàng)造的枝秤。 原因:Child3和父類Parent3共用了一個(gè)原型醋拧。Child本身沒有constructor,由于繼承了父類淀弹,就會(huì)把父類的constructor作為自己的丹壕。<br />解決方案:把上面的

Child3.prototype=Parent3.prototype;

換成

Child3.prototype=Object.create(Parent3.prototype); // 讓Child3繼承Parent3,由于Object.create會(huì)返回一個(gè)新對(duì)象薇溃,該對(duì)象繼承了Parent3菌赖,再讓Child3去繼承Parent3,這樣就起到了隔離并且繼承的作用沐序。
Child3.prototype.constructor=Child3;
// 修改Child3的constructor

這樣的話就是組合繼承+原型繼承的完美寫法了琉用。

// 組合方式,完美寫法
function Parent3(argument) {
    this.name='parent';
    this.age=[1,2,3];
}
Parent3.prototype.say=function(){}
function Child3(argument) {
    Parent3.call(this); // 結(jié)合構(gòu)造函數(shù)
    this.type='test';
}
Child3.prototype=Object.create(Parent3.prototype);
Child3.prototype.constructor=Child3; // 結(jié)合原型鏈
var child31 = new Child3();
var child32 = new Child3();
console.log(child31,child32);
child31.age.push(4);
console.log(child31.age,child32.age);.//  [1, 2, 3, 4]      [1, 2, 3]

<br />

Object.create是做什么的策幼?

  • Object.create()方法創(chuàng)建一個(gè)新對(duì)象邑时,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的proto
const person = {
  isHuman: false,
  printIntroduction: function () {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = Object.create(person);

me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
me.__proto__===person;  // true

一道最近校招面試碰到的和原型相關(guān)的面試題

最近面試某大廠碰到下面這道面試題:

function Page() {
  return this.hosts;
}
Page.hosts = ['h1'];
Page.prototype.hosts = ['h2'];
const p1 = new Page();
const p2 = Page();
console.log(p1.hosts);
console.log(p2.hosts);
復(fù)制代碼

運(yùn)行結(jié)果是:先輸出 undefiend,然后報(bào)錯(cuò) TypeError: Cannot read property 'hosts' of undefined特姐。<br />為什么 console.log(p1.hosts) 是輸出 undefiend 呢晶丘,前面我們提過 new 的時(shí)候如果 return 了對(duì)象,會(huì)直接拿這個(gè)對(duì)象作為 new 的結(jié)果唐含,因此浅浮,p1 應(yīng)該是 this.hosts 的結(jié)果奠货,而在 new Page() 的時(shí)候桩引,this 是一個(gè)以 Page.prototype 為原型的 target 對(duì)象,所以這里 this.hosts 可以訪問到 Page.prototype.hosts 也就是 ['h2']屏轰。這樣 p1 就是等于 ['h2']铜靶,['h2'] 沒有 hosts 屬性所以返回 undefined。<br />為什么 console.log(p2.hosts) 會(huì)報(bào)錯(cuò)呢,p2 是直接調(diào)用 Page 構(gòu)造函數(shù)的結(jié)果争剿,直接調(diào)用 page 函數(shù)已艰,這個(gè)時(shí)候 this 指向全局對(duì)象,全局對(duì)象并沒 hosts 屬性蚕苇,因此返回 undefined哩掺,往 undefined 上訪問 hosts 當(dāng)然報(bào)錯(cuò)。<br />
<br />作者:余騰靖<br />鏈接:https://juejin.im/post/5e2ff7dce51d4558021a1a4d<br />來源:掘金<br />著作權(quán)歸作者所有涩笤。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)嚼吞,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。<br />

proxy是什么蹬碧?

proxy表單校驗(yàn)實(shí)例

proxy實(shí)現(xiàn)觀察者模式


const observerQueue = new Set()

const observe = fn => observerQueue.add(fn)

const observable = obj => new Proxy(obj, 
    set(tgt, key, val, receiver) {
        const result = Reflect.set(tgt, key, val, recevier)
        observerQueue.forEach(v => v())
        return result
    }
)

const person = observerable({age: 25, name: 'Mike'})
const print = () => console.log(`${person.name} is ${person.age} years old`)
observe(print)

person.name = 'LiHua'
// Lihua is 25 years old
person.age = 45
// Lihua is 45 years old

Common.js和ES6的module比較

  • CommonJS 輸出是值的拷貝舱禽,即原來模塊中的值改變不會(huì)影響已經(jīng)加載的該值,ES6靜態(tài)分析恩沽,動(dòng)態(tài)引用誊稚,輸出的是值的引用,值改變罗心,引用也改變里伯,即原來模塊中的值改變則該加載的值也改變。
  • CommonJS 模塊是運(yùn)行時(shí)加載渤闷,ES6 模塊是編譯時(shí)輸出接口疾瓮。
  • CommonJS 加載的是整個(gè)模塊,即將所有的接口全部加載進(jìn)來飒箭,ES6 可以單獨(dú)加載其中的某個(gè)接口(方法)狼电,
  • CommonJS this 指向當(dāng)前模塊,ES6 this 指向undefined
  • CommonJS 模塊輸出的是一個(gè)值的拷貝补憾,ES6 模塊輸出的是值的引用漫萄。

————————————————<br />版權(quán)聲明:本文為CSDN博主「冰雪為融」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議盈匾,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明腾务。<br />原文鏈接:https://blog.csdn.net/lhjuejiang/article/details/80274212<br />

yield傳值與不傳值的區(qū)別是什么?

  • yield每次調(diào)用next的時(shí)候傳值削饵,將作為上一次yield語句后的返回值
  • 如果不傳值則值為undefined
// 傳值
function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}

// 不傳值
function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next()) // => {value: 8, done: false}
console.log(it.next()) // => {value: 42, done: true}
VM5628:7 {value: 6, done: false}
VM5628:8 {value: NaN, done: false}
VM5628:9 {value: NaN, done: true}

yield常見用途

function *fetch() {
    yield ajax(url, () => {})
    yield ajax(url1, () => {})
    yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

async函數(shù)的執(zhí)行順序

  • async中await修飾的語句會(huì)同步執(zhí)行
  • async函數(shù)執(zhí)行之后返回一個(gè)promise岩瘦,里面執(zhí)行的函數(shù)會(huì)作為回調(diào)函數(shù)在外部執(zhí)行棧結(jié)束之后執(zhí)行
let c = 0
let d = async () => {
    c = c + await 10
    console.log('函數(shù)內(nèi)部1')
    await console.log('函數(shù)內(nèi)部2', c)
    console.log('函數(shù)內(nèi)部3')
}
undefined
d()
c++
console.log('函數(shù)外部')
VM2692:3 函數(shù)外部
VM2618:4 函數(shù)內(nèi)部1
VM2618:5 函數(shù)內(nèi)部2 10
VM2618:6 函數(shù)內(nèi)部3

經(jīng)典例題實(shí)現(xiàn)Promise.race和Promise.all

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('p1準(zhǔn)備執(zhí)行')
    resolve('p1執(zhí)行了')
  }, 1000)
})

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('p2執(zhí)行了')
  }, 2000)
})

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p3執(zhí)行失敗了')
  }, 3000)
})

Promise.race([p1, p2, p3]).then(res => console.log(res), err => console.log(err))

p1準(zhǔn)備執(zhí)行
p1執(zhí)行了

Promise.all([p1, p2, p3]).then(res => console.log(res), err => console.log(err))

p1準(zhǔn)備執(zhí)行
p3執(zhí)行失敗了


手寫簡(jiǎn)版的Promise

// <!--  簡(jiǎn)易版的promise -->
const PENDING = "pending"
const RESOLVE = "resolve"
const REJECT = "reject"

function MyPromise(fn) {
  const that = this
  that.status = PENDING // MyPromise 內(nèi)部狀態(tài)
  that.value = null // 傳入 resolve 和 reject 的值
  that.resolveCallbacks = [] // 保存 then 中resolve的回調(diào)函數(shù)
  that.rejectCallbacks = [] // 保存 then 中reject的回調(diào)函數(shù)

  // resolve 函數(shù) Promise內(nèi)部調(diào)用 resolve 函數(shù) 例:new MyPromise((resolve,reject)=>{resolve(1)})
  function resolve(val) {
    if (that.status === PENDING) {
      that.status = RESOLVE
      that.value = val
      that.resolveCallbacks.forEach(cb => cb(that.value))
    }
  }
  // reject 函數(shù) Promise內(nèi)部調(diào)用的 reject 函數(shù) 例:new MyPromise((resolve,reject)=>{reject(1)})
  function reject(val) {
    if (that.status === PENDING) {
      that.status = REJECT
      that.value = val
      that.rejectCallbacks.forEach(cb => cb(that.value))
    }
  }
  // 調(diào)用傳入 MyPromise 內(nèi)的方法 例:new MyPromise((resolve,reject)=>{})   fn=(resolve,reject)=>{}
  try {
    fn(resolve, reject)
  } catch (error) {
    reject(error)
  }
}
// 在原型上添加then方法
MyPromise.prototype.then = function (onFulfilled, onRejected) {
  const that = this
  // 判斷傳入的是否為函數(shù)
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected === 'function' ? onRejected : r => {
    throw r
  }

  //如果 Promise 內(nèi)部存在異步代碼,調(diào)用then方法時(shí)窿撬,此時(shí) promise 內(nèi)部還是 PENDING 狀態(tài)启昧,
  將 then 里面的函數(shù)添加進(jìn)回調(diào)數(shù)組,當(dāng)異步處理完成后調(diào)用 MyPromise 內(nèi)部的 resolve 或者
  reject 函數(shù)
  if (that.status === PENDING) {
    that.resolveCallbacks.push(onFulfilled)
    that.rejectCallbacks.push(onRejected)
  }

  // 當(dāng) Promise 內(nèi)部的狀態(tài)已經(jīng)為 resolve,則調(diào)用 then 里面的函數(shù)并傳遞值
  if (that.status === RESOLVE) {
    onFulfilled(that.value)
  }

  // 當(dāng) Promise 內(nèi)部狀態(tài)為 reject,則調(diào)用then里的回調(diào)函數(shù)并傳遞值
  if (that.status === REJECT) {
    onRejected(that.value)
  }
}

// 自己實(shí)現(xiàn)的Promise
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
    reject(2)
  }, 0)
}).then(res => {
  console.log(res)
}, err => {
  console.log(err)
})
// MyPromise.resolve(4).then().then(res => console.log(4)) // 透?jìng)髋椋形磳?shí)現(xiàn)

async和await遇見EventsLoop的注意事項(xiàng)

一般而言,我們可以把

async function f() {
  await p
  console.log('ok')
}
簡(jiǎn)化理解為:

function f() {
  return RESOLVE(p).then(() => {
    console.log('ok')
  })
}
『RESOLVE(p)』接近于『Promise.resolve(p)』,不過有微妙而重要的區(qū)別:
p 如果本身已經(jīng)是 Promise 實(shí)例严里,Promise.resolve 會(huì)直接返回 p 而不是產(chǎn)生一個(gè)新 promise新啼。
【個(gè)人認(rèn)為 Promise.resolve 的這一『優(yōu)化』行為可能是弊大于利的,不過因?yàn)橐呀?jīng)寫進(jìn)標(biāo)準(zhǔn)了刹碾,
也不太可能修改了燥撞。】老版本V8的問題是當(dāng) p 是一個(gè)已經(jīng) settled 的 promise迷帜,會(huì)進(jìn)行類似的激進(jìn)優(yōu)化物舒,
導(dǎo)致執(zhí)行時(shí)序與非 settled 的 promise 結(jié)果不同。比如把你的 async2 中加入一個(gè) await 語句戏锹,
老版本行為就和新版本一致了冠胯。

例題:

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

setTimeout(function() {
  console.log('setTimeout')
}, 0)

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

解析:

當(dāng)await 后面是一個(gè)async函數(shù)并且執(zhí)行之后,也就是:
async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}

由于async2執(zhí)行后返回一個(gè)promise景用,則相當(dāng)于
async function async1() {
   return new Promise((res, rej) => {
     console.log('async2 end')
  }).then(() => {
     console.log('async1 end')
 })
}

結(jié)果一樣

null和undefined有何區(qū)別

  • null表示有值涵叮,但是為空,Number(null) === 1 false
  • undefined表示沒有值伞插,還沒有定義割粮,Number(undefined) === NaN false

如何正確判斷業(yè)務(wù)中遇到的undefined值

  • 注意:避免使用==,因?yàn)?/li>
null == undefined
true
0 == undefined
false
'' == undefined
false
  • 正確判斷方法
1媚污、使用====
  if(backgroundAudioManger === undefined) ...
2舀瓢、使用typeof
    if(typeof backgroundAdudioManager == 'undefined')

手寫實(shí)現(xiàn)call,apply,bind

  • call
Function.prototype.myCall = function(context) {
  if ( typeof this !== 'function' ) throw new TypeError('Error')
  // 完善部分,如果傳入context是個(gè)基礎(chǔ)類型是無法綁定fn函數(shù)的耗美,所以
  
            if (typeof context === 'object') {
                context = context || window;
            } else {
                context = Object.create(null)
            }

  context = context || window
  // 如果context中有fn則會(huì)被覆蓋并清除
  // newContext.fn = this
  // 使用Symbol()獨(dú)一無二數(shù)據(jù)類型避免fn沖突
  let fn = Symbol('fn')
  context[fn] = this
  let args
  let result
  if ([...arguments][1]) {
    args = [...arguments].slice(1)
    result = newContext.fn(args)
  } else {
    result = newContext.fn()
  }
  delete context[fn]
  return result
}

function fn () {
  console.log(this.a, this)
}

const obj = {
  a: 21
}

fn.myCall(obj)
  • apply
Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  let result
  // 處理參數(shù)和 call 有區(qū)別
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

  • bind
Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一個(gè)函數(shù)
  return function F() {
    // 因?yàn)榉祷亓艘粋€(gè)函數(shù)京髓,我們可以 new F(),所以需要判斷
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

手寫實(shí)現(xiàn)一個(gè)new方法

  • new原理那部分商架,鏈接到原型堰怨,obj = Object.create(Con.prototype),這種方式會(huì)比較好
function _new(fn,...arg){
    let obj = {}
    let con = [].slice.call(arguments)
    obj.__proto__ = con.prototype //鏈接原型
    const ret = fn.call(obj, ...arg); //改變this的指向
    return ret instanceof Object ? ret : obj;
}

Symbol()和Symbol.for()和Symbol.keyFor()區(qū)別

  • 參考鏈接:鏈接
  • Symbol()返回一個(gè)獨(dú)一無二的標(biāo)識(shí)符類型值
  • Symbol.for()蛇摸,將傳入值生成一個(gè)標(biāo)識(shí)符類性值备图,并將其作為key存入到注冊(cè)表中
  • Symbol.keyFor(),傳入一個(gè)Symbol.for()生成的標(biāo)識(shí)符類型值赶袄,返回注冊(cè)表中對(duì)應(yīng)該key的value值(Symbol.for(‘value’))
  • image.png
    image.png

巧用parseFloat避免js浮點(diǎn)數(shù)精度轉(zhuǎn)換問題

(0.1 + 0.2).toFixed(10)
"0.3000000000"
parseFloat((0.1 + 0.2).toFixed(10))
0.3

更簡(jiǎn)便的手寫call

Function.prototype.myCall = function(context,...args){
  context  = context || window
  const symbol = Symbol()
  context[symbol] = this
  const result = context[symbol](...args)
  delete context[symbol]
  return result
}

[].shift.call(arguments)是什么原理呢揽涮?

改變綁定的 this:

let a = [1,2,3]
const myshift = [].shift.bind(a)
console.log(myshift(a))

  • Arguments是一個(gè)類數(shù)組對(duì)線,不能直接進(jìn)行數(shù)組想關(guān)的操作饿肺,使用[].shifit.call(arguments)則可以轉(zhuǎn)化為數(shù)組之后進(jìn)行一些操作
function fn3 (a, b) {
    console.log(arguments)
    console.log([...arguments])
    console.log([].shift.call(arguments))
}
image.png
image.png

<br />

<br />如果傳入的參數(shù)為函數(shù)蒋困,則[].shift.call(arguments)會(huì)彈出這個(gè)函數(shù)<br />
image.png
image.png

<br />

事件代理應(yīng)用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>手寫函數(shù)測(cè)試</title>
</head>
<body>
<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
<script>
  let ul = document.querySelector('#ul')
  ul.addEventListener('click', (event) => {
    console.log(event.target);
  })
</script>
</body>
</html>

阻止事件冒泡

  • 操作:在子DOM節(jié)點(diǎn)的event執(zhí)行事件上綁定stopPropagation
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>手寫函數(shù)測(cè)試</title>
</head>
<body>
<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li id="li">4</li>
  <li>5</li>
</ul>
<script>
  let ul = document.querySelector('#ul')
  ul.addEventListener('click', (event) => {
    console.log(event);
  }) // 如果在后面配置項(xiàng)為true, 則先捕獲(父節(jié)點(diǎn)事件)后冒泡(子節(jié)點(diǎn)事件)
  let li = document.querySelector('#li')
  li.addEventListener('click', (event) => {
    console.log(event, '4點(diǎn)擊了')
    event.stopPropagation() // 阻止其余捕獲
  })
</script>
</body>
</html>

冒泡事件和捕獲事件順序調(diào)換的配置

  • 默認(rèn)false
<div>
    外層div
    <p>內(nèi)層p</p>
<div>
var div = document.querySelector('div');
var p = document.querySelector('p');

div.addEventListener('click',  function() {console.log("冒泡")}, false);
div.addEventListener('click',  function() {console.log("捕獲")}, true);

點(diǎn)擊div, 輸出 冒泡敬辣,捕獲
點(diǎn)擊p, 輸出捕獲雪标,冒泡

<br />

瀏覽器同源策略要干啥的零院, 為什么要跨域請(qǐng)求?

  • 當(dāng)我們?cè)?a target="_blank">www.a.com這個(gè)域下用ajax提交一個(gè)請(qǐng)求到www.b.com這個(gè)域的時(shí)候村刨,默認(rèn)情況下门粪,瀏覽器是不允許的,因?yàn)檫`反了瀏覽器的同源策略烹困。
  • 參考地址:https://www.cnblogs.com/anai/p/4227157.html

跨域解決方案

  • Jsonp
  • Cors白名單
  • document.domain(適用于二級(jí)域名相同--->a.test.com 和 b.test.com)
  • postMessage
  • 前端proxy正向代理
  • 后端proxy反向代理

cookie,localStorage乾吻,sessionStorage區(qū)別

  • cookie:多為網(wǎng)絡(luò)請(qǐng)求過程中服務(wù)器通過請(qǐng)求信息在瀏覽器客戶端種下髓梅,可以用來保存用戶登錄狀態(tài),規(guī)避http請(qǐng)求無狀態(tài)缺陷绎签,缺陷是每一個(gè)name對(duì)應(yīng)的value值大小只有4k枯饿,可存儲(chǔ)量太小,超過會(huì)被裁剪诡必,而且每次請(qǐng)求都會(huì)攜帶奢方,造成額外的帶寬占用
  • localStorage:除非手動(dòng)清空,否則用于存在于本地存儲(chǔ)中爸舒,可在同源標(biāo)簽頁下共享蟋字,適用于一些長(zhǎng)久不變的數(shù)據(jù),比如一些電商網(wǎng)站的base64數(shù)據(jù)扭勉,大小約為5兆左右
  • sessionStorage:只在當(dāng)前窗口下存在鹊奖,同源標(biāo)簽下不共享,瀏覽器窗口關(guān)閉會(huì)被自動(dòng)清空涂炎,適用于需要每次打開都重新登錄的網(wǎng)站存儲(chǔ)JWT的token數(shù)據(jù)忠聚。大小約為5兆左右
  • 區(qū)別:sessionStorage保存的數(shù)據(jù)用于瀏覽器的一次會(huì)話,當(dāng)會(huì)話結(jié)束(通常是該窗口關(guān)閉)唱捣,數(shù)據(jù)被清空两蟀;sessionStorage 特別的一點(diǎn)在于,即便是相同域名下的兩個(gè)頁面震缭,只要它們不在同一個(gè)瀏覽器窗口中打開赂毯,那么它們的 sessionStorage 內(nèi)容便無法共享;localStorage 在所有同源窗口中都是共享的蛀序;cookie也是在所有同源窗口中都是共享的欢瞪。除了保存期限的長(zhǎng)短不同,SessionStorage的屬性和方法與LocalStorage完全一樣徐裸。
  • 參考:https://github.com/ljianshu/Blog/issues/25

<br />

知道哪些新的緩存方案遣鼓?

代理的工作原理解析

<br />

  • webpack中的proxy本地代理原理
  • 使用了express框架搭建本web項(xiàng)目服務(wù)的時(shí)候怯晕,引入了http-proxy-middleware的中間件,參考鏈接http://www.reibang.com/p/8fd5d7347c57
  • image.png
    image.png

node服務(wù)后臺(tái)接收預(yù)檢請(qǐng)求防止報(bào)錯(cuò)

if(res.method == 'OPTIONS') {
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
} else {
next()
}

瀏覽器緩存機(jī)制(后端向)

[圖片上傳失敗...(image-f02655-1611839965980)]<br />

瀏覽器渲染原理

參考地址:https://github.com/ljianshu/Blog/issues/51

  • 接收信息解析代碼過程:

[圖片上傳失敗...(image-f04011-1611839965980)]

  1. 接收HTML文件缸棵,構(gòu)建DOM樹
  2. 接收CSS文件舟茶,構(gòu)建CSSOM樹
  3. 接收jS文件(加載js腳本),等到Javascript 腳本文件加載后堵第, 通過 DOM API 和 CSSOM API 來操作 DOM Tree 和 CSS Rule Tree吧凉。

<br />

  • 注意:在構(gòu)建DOM時(shí),HTML解析器若遇到了JavaScript踏志,那么它會(huì)暫停構(gòu)建DOM阀捅,將控制權(quán)移交給JavaScript引擎,等JavaScript引擎運(yùn)行完畢针余,瀏覽器再從中斷的地方恢復(fù)DOM構(gòu)建饲鄙。
  • 瀏覽器會(huì)先下載和構(gòu)建CSSOM,然后再執(zhí)行JavaScript圆雁,最后在繼續(xù)構(gòu)建DOM忍级。

<br />

性能優(yōu)化建議:

  • 還有js中操作dom元素的樣式時(shí),如果修改多條style伪朽,不如把多條style寫成一個(gè)class颤练,對(duì)元素進(jìn)行添加class的操作。如果是分步添加元素樣式驱负,可以將元素賦值給一個(gè)變量嗦玖,避免多次獲取一個(gè)元素,因?yàn)楂@取元素也會(huì)造成回流跃脊。
    1. 不要一條一條的修改 DOM 樣式宇挫,盡量提前設(shè)置好 class,后續(xù)增加 class酪术,進(jìn)行批量修改器瘪。
    1. 使用 documentFragment 對(duì)象在內(nèi)存里操作 DOM
    1. 不一定說非要用visibility: hidden替換display:none,完全可以把修改頻繁的元素先 display: none绘雁,修改完之后顯示橡疼。

RAF討論

  • 屏幕每16.6ms才刷新一次,比如這一幀有個(gè)人站著庐舟,下一幀他應(yīng)該要邁開左腿欣除,如果都已經(jīng)下一幀了,瀏覽器還沒渲染出邁開腿的畫面挪略,這個(gè)人還站著历帚,看著就卡了滔岳。然后event loop和渲染這邊,我的理解是挽牢,在短短16.6ms內(nèi)谱煤,要保證一個(gè)tick能執(zhí)行完并將這個(gè)tick里對(duì)dom的操作渲染出來(也因此同個(gè)tick里的多個(gè)dom操作會(huì)被合并),這樣下一幀才能有邁開腿的畫面出現(xiàn)在屏幕上禽拔。如果當(dāng)前的tick要花好久才能執(zhí)行完刘离,就說40ms吧,那至少兩幀都只有人站著的畫面睹栖,第三幀還剩短短10ms用來重新渲染寥闪,如果能渲染出來還好,下一幀就能看磨淌,否則下一幀還看不到變化。
  • 所以凿渊,假設(shè)一個(gè)tick里js先往某個(gè)<ul>里添加了幾個(gè)<li>子節(jié)點(diǎn)梁只,接著還要做一件非常耗時(shí)的事情,兩件事合在一起要好久才能完成埃脏,不如把兩件事拆開搪锣,放到兩個(gè)requestAnimationFrame的回調(diào)里執(zhí)行,這樣就能先把對(duì)dom的修改重新渲染處理彩掐,下一幀至少能看到變化了构舟。

<br />

前端安全

參考:XSS攻擊和CSRF攻擊

XSS:https://juejin.im/post/5bad9140e51d450e935c6d64<br />CSRF:https://juejin.im/post/5bc009996fb9a05d0a055192#heading-32<br />

了解webpack原理,手寫小型webpack

流程淺析:參考

  1. createAsset:讀取入口文件堵幽,通過babylon的parse方法將文件內(nèi)容字符串轉(zhuǎn)化為AST語法樹(包含id,filename,dependencies,code信息)的一種數(shù)據(jù)結(jié)構(gòu)
  2. createGraph:遍歷入門文件AST語法樹上的dependencies數(shù)組狗超,生成一個(gè)新的數(shù)組,數(shù)組每一項(xiàng)和createAsset方法創(chuàng)建出來的AST語法樹非常相像朴下,并且多了一個(gè)mapping屬性指定文件依賴努咐,數(shù)組中將依賴文件根據(jù)依賴順序id遞增排列。
  3. bundle:接收graph數(shù)組殴胧,生成bundle結(jié)構(gòu)的自執(zhí)行函數(shù)
  4. 代碼地址:https://github.com/dykily/simple_webpack/blob/327618d4e2/bundler.js
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末渗稍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子团滥,更是在濱河造成了極大的恐慌竿屹,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灸姊,死亡現(xiàn)場(chǎng)離奇詭異拱燃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)力惯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門扼雏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坚嗜,“玉大人,你說我怎么就攤上這事诗充〔允撸” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵蝴蜓,是天一觀的道長(zhǎng)碟绑。 經(jīng)常有香客問我,道長(zhǎng)茎匠,這世上最難降的妖魔是什么格仲? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮诵冒,結(jié)果婚禮上凯肋,老公的妹妹穿的比我還像新娘。我一直安慰自己汽馋,他們只是感情好侮东,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豹芯,像睡著了一般悄雅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铁蹈,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天宽闲,我揣著相機(jī)與錄音,去河邊找鬼握牧。 笑死容诬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沿腰。 我是一名探鬼主播放案,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼矫俺!你這毒婦竟也來了吱殉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤厘托,失蹤者是張志新(化名)和其女友劉穎友雳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铅匹,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡押赊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片流礁。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涕俗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出神帅,到底是詐尸還是另有隱情再姑,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布找御,位于F島的核電站元镀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏霎桅。R本人自食惡果不足惜栖疑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滔驶。 院中可真熱鬧遇革,春花似錦、人聲如沸揭糕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽插佛。三九已至,卻和暖如春量窘,著一層夾襖步出監(jiān)牢的瞬間雇寇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工蚌铜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锨侯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓冬殃,卻偏偏與公主長(zhǎng)得像囚痴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子审葬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容