摘要:當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。作用域與作用域鏈到來有全局作用域函數(shù)作用域和塊級作用域新增。
引言
Javascript是前端面試的重點,本文重點梳理下 Javascript 中的??贾R點,然后就一些容易出現(xiàn)的題目進行解析。限于文章的篇幅,無法將知識點講解的面面俱到,本文只羅列了一些重難點,如果想要了解更多內(nèi)容歡迎點擊我的博客。
一、變量類型 1.JS 的數(shù)據(jù)類型分類根據(jù) JavaScript 中的變量類型傳遞方式,分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。其中基本數(shù)據(jù)類型包括Undefined、Null、Boolean、Number、String、Symbol (ES6新增,表示獨一無二的值),而引用數(shù)據(jù)類型統(tǒng)稱為Object對象,主要包括對象、數(shù)組和函數(shù)。
在參數(shù)傳遞方式上,有所不同:
函數(shù)的參數(shù)如果是簡單類型,會將一個值類型的數(shù)值副本傳到函數(shù)內(nèi)部,函數(shù)內(nèi)部不影響函數(shù)外部傳遞的參數(shù)變量
如果是一個參數(shù)是引用類型,會將引用類型的地址值復(fù)制給傳入函數(shù)的參數(shù),函數(shù)內(nèi)部修改會影響傳遞
題目:基本類型和引用類型的區(qū)別
基本類型和引用類型存儲于內(nèi)存的位置不同,基本類型直接存儲在棧中,而引用類型的對象存儲在堆中,與此同時,在棧中存儲了指針,而這個指針指向正是堆中實體的起始位置。下面通過一個小題目,來看下兩者的主要區(qū)別:
// 基本類型 var a = 10 var b = a b = 20 console.log(a) // 10 console.log(b) // 20
上述代碼中,a b都是值類型,兩者分別修改賦值,相互之間沒有任何影響。再看引用類型的例子:
// 引用類型 var a = {x: 10, y: 20} var b = a b.x = 100 b.y = 200 console.log(a) // {x: 100, y: 200} console.log(b) // {x: 100, y: 200}
上述代碼中,a b都是引用類型。在執(zhí)行了b = a之后,修改b的屬性值,a的也跟著變化。因為a和b都是引用類型,指向了同一個內(nèi)存地址,即兩者引用的是同一個值,因此b修改屬性時,a的值隨之改動
2.數(shù)據(jù)類型的判斷 1)typeoftypeof返回一個表示數(shù)據(jù)類型的字符串,返回結(jié)果包括:number、boolean、string、symbol、object、undefined、function等7種數(shù)據(jù)類型,但不能判斷null、array等
typeof Symbol(); // symbol 有效 typeof ""; // string 有效 typeof 1; // number 有效 typeof true; //boolean 有效 typeof undefined; //undefined 有效 typeof new Function(); // function 有效 typeof null; //object 無效 typeof [] ; //object 無效 typeof new Date(); //object 無效 typeof new RegExp(); //object 無效2)instanceof
instanceof 是用來判斷A是否為B的實例,表達式為:A instanceof B,如果A是B的實例,則返回true,否則返回false。instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構(gòu)造函數(shù)的 prototype 屬性,但它不能檢測null 和 undefined
[] instanceof Array; //true {} instanceof Object;//true new Date() instanceof Date;//true new RegExp() instanceof RegExp//true null instanceof Null//報錯 undefined instanceof undefined//報錯3)constructor
constructor作用和instanceof非常相似。但constructor檢測 Object與instanceof不一樣,還可以處理基本數(shù)據(jù)類型的檢測。
不過函數(shù)的 constructor 是不穩(wěn)定的,這個主要體現(xiàn)在把類的原型進行重寫,在重寫的過程中很有可能出現(xiàn)把之前的constructor給覆蓋了,這樣檢測出來的結(jié)果就是不準(zhǔn)確的。
Object.prototype.toString.call() 是最準(zhǔn)確最常用的方式。
Object.prototype.toString.call("") ; // [object String] Object.prototype.toString.call(1) ; // [object Number] Object.prototype.toString.call(true) ; // [object Boolean] Object.prototype.toString.call(undefined) ; // [object Undefined] Object.prototype.toString.call(null) ; // [object Null] Object.prototype.toString.call(new Function()) ; // [object Function] Object.prototype.toString.call(new Date()) ; // [object Date] Object.prototype.toString.call([]) ; // [object Array] Object.prototype.toString.call(new RegExp()) ; // [object RegExp] Object.prototype.toString.call(new Error()) ; // [object Error]3.淺拷貝與深拷貝
淺拷貝只復(fù)制指向某個對象的指針,而不復(fù)制對象本身,新舊對象還是共享同一塊內(nèi)存。
淺拷貝的實現(xiàn)方式(詳見淺拷貝與深拷貝):
Object.assign():需注意的是目標(biāo)對象只有一層的時候,是深拷貝
Array.prototype.concat()
Array.prototype.slice()
深拷貝就是在拷貝數(shù)據(jù)的時候,將數(shù)據(jù)的所有引用結(jié)構(gòu)都拷貝一份。簡單的說就是,在內(nèi)存中存在兩個數(shù)據(jù)結(jié)構(gòu)完全相同又相互獨立的數(shù)據(jù),將引用型類型進行復(fù)制,而不是只復(fù)制其引用關(guān)系。
深拷貝的實現(xiàn)方式:
熱門的函數(shù)庫lodash,也有提供_.cloneDeep用來做深拷貝
jquery 提供一個$.extend可以用來做深拷貝
JSON.parse(JSON.stringify())
手寫遞歸方法
遞歸實現(xiàn)深拷貝的原理:要拷貝一個數(shù)據(jù),我們肯定要去遍歷它的屬性,如果這個對象的屬性仍是對象,繼續(xù)使用這個方法,如此往復(fù)。
//定義檢測數(shù)據(jù)類型的功能函數(shù) function checkedType(target) { return Object.prototype.toString.call(target).slice(8, -1) } //實現(xiàn)深度克隆---對象/數(shù)組 function clone(target) { //判斷拷貝的數(shù)據(jù)類型 //初始化變量result 成為最終克隆的數(shù)據(jù) let result, targetType = checkedType(target) if (targetType === "Object") { result = {} } else if (targetType === "Array") { result = [] } else { return target } //遍歷目標(biāo)數(shù)據(jù) for (let i in target) { //獲取遍歷數(shù)據(jù)結(jié)構(gòu)的每一項值。 let value = target[i] //判斷目標(biāo)結(jié)構(gòu)里的每一值是否存在對象/數(shù)組 if (checkedType(value) === "Object" || checkedType(value) === "Array") { //對象/數(shù)組里嵌套了對象/數(shù)組 //繼續(xù)遍歷獲取到value值 result[i] = clone(value) } else { //獲取到value值是基本的數(shù)據(jù)類型或者是函數(shù)。 result[i] = value } } return result }二、作用域和閉包 1.執(zhí)行上下文和執(zhí)行棧
執(zhí)行上下文就是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時所在環(huán)境的抽象概念, JavaScript 中運行任何的代碼都是在執(zhí)行上下文中運行。
執(zhí)行上下文的生命周期包括三個階段:創(chuàng)建階段→執(zhí)行階段→回收階段,我們重點介紹創(chuàng)建階段。
創(chuàng)建階段(當(dāng)函數(shù)被調(diào)用,但未執(zhí)行任何其內(nèi)部代碼之前)會做以下三件事:
創(chuàng)建變量對象:首先初始化函數(shù)的參數(shù)arguments,提升函數(shù)聲明和變量聲明。
創(chuàng)建作用域鏈:下文會介紹
確定this指向:下文會介紹
function test(arg){ // 1. 形參 arg 是 "hi" // 2. 因為函數(shù)聲明比變量聲明優(yōu)先級高,所以此時 arg 是 function console.log(arg); var arg = "hello"; // 3.var arg 變量聲明被忽略, arg = "hello"被執(zhí)行 function arg(){ console.log("hello world") } console.log(arg); } test("hi"); /* 輸出: function arg() { console.log("hello world"); } hello */
這是因為當(dāng)函數(shù)執(zhí)行的時候,首先會形成一個新的私有的作用域,然后依次按照如下的步驟執(zhí)行:
如果有形參,先給形參賦值
進行私有作用域中的預(yù)解釋,函數(shù)聲明優(yōu)先級比變量聲明高,最后后者會被前者所覆蓋,但是可以重新賦值
私有作用域中的代碼從上到下執(zhí)行
函數(shù)多了,就有多個函數(shù)執(zhí)行上下文,每次調(diào)用函數(shù)創(chuàng)建一個新的執(zhí)行上下文,那如何管理創(chuàng)建的那么多執(zhí)行上下文呢?
JavaScript 引擎創(chuàng)建了執(zhí)行棧來管理執(zhí)行上下文。可以把執(zhí)行棧認(rèn)為是一個存儲函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進后出的原則。
從上面的流程圖,我們需要記住幾個關(guān)鍵點:
JavaScript執(zhí)行在單線程上,所有的代碼都是排隊執(zhí)行。
一開始瀏覽器執(zhí)行全局的代碼時,首先創(chuàng)建全局的執(zhí)行上下文,壓入執(zhí)行棧的頂部。
每當(dāng)進入一個函數(shù)的執(zhí)行就會創(chuàng)建函數(shù)的執(zhí)行上下文,并且把它壓入執(zhí)行棧的頂部。當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。
瀏覽器的JS執(zhí)行引擎總是訪問棧頂?shù)膱?zhí)行上下文。
全局上下文只有唯一的一個,它在瀏覽器關(guān)閉時出棧。
2.作用域與作用域鏈ES6 到來JavaScript 有全局作用域、函數(shù)作用域和塊級作用域(ES6新增)。我們可以這樣理解:作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說作用域最大的用處就是隔離變量,不同作用域下同名變量不會有沖突。
在介紹作用域鏈之前,先要了解下自由變量,如下代碼中,console.log(a)要得到a變量,但是在當(dāng)前的作用域中沒有定義a(可對比一下b)。當(dāng)前作用域沒有定義的變量,這成為 自由變量。
var a = 100 function fn() { var b = 200 console.log(a) // 這里的a在這里就是一個自由變量 console.log(b) } fn()
自由變量的值如何得到 —— 向父級作用域(創(chuàng)建該函數(shù)的那個父級作用域)尋找。如果父級也沒呢?再一層一層向上尋找,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關(guān)系,就是作用域鏈 。
function F1() { var a = 100 return function () { console.log(a) } } function F2(f1) { var a = 200 console.log(f1()) } var f1 = F1() F2(f1) // 100
上述代碼中,自由變量a的值,從函數(shù)F1中查找而不是F2,這是因為當(dāng)自由變量從作用域鏈中去尋找,依據(jù)的是函數(shù)定義時的作用域鏈,而不是函數(shù)執(zhí)行時。
3.閉包是什么閉包這個概念也是JavaScript中比較抽象的概念,我個人理解,閉包是就是函數(shù)中的函數(shù)(其他語言不能這樣),里面的函數(shù)可以訪問外面函數(shù)的變量,外面的變量的是這個內(nèi)部函數(shù)的一部分。
閉包的作用:
使用閉包可以訪問函數(shù)中的變量。
可以使變量長期保存在內(nèi)存中,生命周期比較長。
閉包不能濫用,否則會導(dǎo)致內(nèi)存泄露,影響網(wǎng)頁的性能。閉包使用完了后,要立即釋放資源,將引用變量指向null。
閉包主要有兩個應(yīng)用場景:
函數(shù)作為參數(shù)傳遞(見作用域部分例子)
函數(shù)作為返回值(如下例)
function outer() { var num = 0 //內(nèi)部變量 return function add() { //通過return返回add函數(shù),就可以在outer函數(shù)外訪問了。 num++ //內(nèi)部函數(shù)有引用,作為add函數(shù)的一部分了 console.log(num) } } var func1 = outer() // func1() //實際上是調(diào)用add函數(shù), 輸出1 func1() //輸出2 var func2 = outer() func2() // 輸出1 func2() // 輸出24.this全面解析
先搞明白一個很重要的概念 —— this的值是在執(zhí)行的時候才能確認(rèn),定義的時候不能確認(rèn)! 為什么呢 —— 因為this是執(zhí)行上下文環(huán)境的一部分,而執(zhí)行上下文需要在代碼執(zhí)行之前確定,而不是定義的時候??慈缦吕樱?/p>
// 情況1 function foo() { console.log(this.a) //1 } var a = 1 foo() // 情況2 function fn(){ console.log(this); } var obj={fn:fn}; obj.fn(); //this->obj // 情況3 function CreateJsPerson(name,age){ //this是當(dāng)前類的一個實例p1 this.name=name; //=>p1.name=name this.age=age; //=>p1.age=age } var p1=new CreateJsPerson("尹華芝",48); // 情況4 function add(c, d){ return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34 // 情況5
接下來我們逐一解釋上面幾種情況
對于直接調(diào)用 foo 來說,不管 foo 函數(shù)被放在了什么地方,this 一定是 window
對于 obj.foo() 來說,我們只需要記住,誰調(diào)用了函數(shù),誰就是 this,所以在這個場景下 foo 函數(shù)中的 this 就是 obj 對象
在構(gòu)造函數(shù)模式中,類中(函數(shù)體中)出現(xiàn)的this.xxx=xxx中的this是當(dāng)前類的一個實例
call、apply和bind:this 是第一個參數(shù)
箭頭函數(shù)this指向:箭頭函數(shù)沒有自己的this,看其外層的是否有函數(shù),如果有,外層函數(shù)的this就是內(nèi)部箭頭函數(shù)的this,如果沒有,則this是window。
三、異步 1.同步 vs 異步同步,我的理解是一種線性執(zhí)行的方式,執(zhí)行的流程不能跨越。比如說話后在吃飯,吃完飯后在看手機,必須等待上一件事完了,才執(zhí)行后面的事情。
異步,是一種并行處理的方式,不必等待一個程序執(zhí)行完,可以執(zhí)行其它的任務(wù)。比方說一個人邊吃飯,邊看手機,邊說話,就是異步處理的方式。在程序中異步處理的結(jié)果通常使用回調(diào)函數(shù)來處理結(jié)果。
// 同步 console.log(100) alert(200); console.log(300) //100 200 300
// 異步 console.log(100) setTimeout(function(){ console.log(200) }) console.log(300) //100 300 2002.異步和單線程
JS 需要異步的根本原因是 JS 是單線程運行的,即在同一時間只能做一件事,不能“一心二用”。為了利用多核CPU的計算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)。
一個 Ajax 請求由于網(wǎng)絡(luò)比較慢,請求需要 5 秒鐘。如果是同步,這 5 秒鐘頁面就卡死在這里啥也干不了了。異步的話,就好很多了,5 秒等待就等待了,其他事情不耽誤做,至于那 5 秒鐘等待是網(wǎng)速太慢,不是因為 JS 的原因。
3.前端異步的場景前端使用異步的場景
定時任務(wù):setTimeout,setInterval
網(wǎng)絡(luò)請求:ajax請求,動態(tài)加載
事件綁定
4.Event Loop一個完整的 Event Loop 過程,可以概括為以下階段:
一開始執(zhí)行???我們可以把執(zhí)行棧認(rèn)為是一個存儲函數(shù)調(diào)用的棧結(jié)構(gòu),遵循先進后出的原則。micro 隊列空,macro 隊列里有且只有一個 script 腳本(整體代碼)。
全局上下文(script 標(biāo)簽)被推入執(zhí)行棧,同步代碼執(zhí)行。在執(zhí)行的過程中,會判斷是同步任務(wù)還是異步任務(wù),通過對一些接口的調(diào)用,可以產(chǎn)生新的 macro-task 與 micro-task,它們會分別被推入各自的任務(wù)隊列里。同步代碼執(zhí)行完了,script 腳本會被移出 macro 隊列,這個過程本質(zhì)上是隊列的 macro-task 的執(zhí)行和出隊的過程。
上一步我們出隊的是一個 macro-task,這一步我們處理的是 micro-task。但需要注意的是:當(dāng) macro-task 出隊時,任務(wù)是一個一個執(zhí)行的;而 micro-task 出隊時,任務(wù)是一隊一隊執(zhí)行的。因此,我們處理 micro 隊列這一步,會逐個執(zhí)行隊列中的任務(wù)并把它出隊,直到隊列被清空。
執(zhí)行渲染操作,更新界面
檢查是否存在 Web worker 任務(wù),如果有,則對其進行處理
上述過程循環(huán)往復(fù),直到兩個隊列都清空
接下來我們看道例子來介紹上面流程:
Promise.resolve().then(()=>{ console.log("Promise1") setTimeout(()=>{ console.log("setTimeout2") },0) }) setTimeout(()=>{ console.log("setTimeout1") Promise.resolve().then(()=>{ console.log("Promise2") }) },0)
最后輸出結(jié)果是Promise1,setTimeout1,Promise2,setTimeout2
一開始執(zhí)行棧的同步任務(wù)(這屬于宏任務(wù))執(zhí)行完畢,會去查看是否有微任務(wù)隊列,上題中存在(有且只有一個),然后執(zhí)行微任務(wù)隊列中的所有任務(wù)輸出Promise1,同時會生成一個宏任務(wù) setTimeout2
然后去查看宏任務(wù)隊列,宏任務(wù) setTimeout1 在 setTimeout2 之前,先執(zhí)行宏任務(wù) setTimeout1,輸出 setTimeout1
在執(zhí)行宏任務(wù)setTimeout1時會生成微任務(wù)Promise2 ,放入微任務(wù)隊列中,接著先去清空微任務(wù)隊列中的所有任務(wù),輸出 Promise2
清空完微任務(wù)隊列中的所有任務(wù)后,就又會去宏任務(wù)隊列取一個,這回執(zhí)行的是 setTimeout2
四、原型鏈與繼承 1.原型和原型鏈原型:在JavaScript中原型是一個prototype對象,用于表示類型之間的關(guān)系。
原型鏈:JavaScript萬物都是對象,對象和對象之間也有關(guān)系,并不是孤立存在的。對象之間的繼承關(guān)系,在JavaScript中是通過prototype對象指向父類對象,直到指向Object對象為止,這樣就形成了一個原型指向的鏈條,專業(yè)術(shù)語稱之為原型鏈。
var Person = function() { this.age = 18 this.name = "匿名" } var Student = function() {} //創(chuàng)建繼承關(guān)系,父類實例作為子類原型 Student.prototype = new Person() var s1 = new Student() console.log(s1)
原型關(guān)系圖:
當(dāng)試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么會去它的__proto__(即它的構(gòu)造函數(shù)的prototype)中尋找。如果一直找到最上層都沒有找到,那么就宣告失敗,返回undefined。最上層是什么 —— Object.prototype.__proto__ === null
介紹幾種常見繼承方式(如需了解更多,請點擊JavaScript常見的六種繼承方式):
原型鏈+借用構(gòu)造函數(shù)的組合繼承
function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = new Parent() const child = new Child(1) child.getValue() // 1 child instanceof Parent // true
以上繼承的方式核心是在子類的構(gòu)造函數(shù)中通過 Parent.call(this) 繼承父類的屬性,然后改變子類的原型為 new Parent() 來繼承父類的函數(shù)。
這種繼承方式優(yōu)點在于構(gòu)造函數(shù)可以傳參,不會與父類引用屬性共享,可以復(fù)用父類的函數(shù),但是也存在一個缺點就是在繼承父類函數(shù)的時候調(diào)用了父類構(gòu)造函數(shù),導(dǎo)致子類的原型上多了不需要的父類屬性,存在內(nèi)存上的浪費。
寄生組合繼承:這種繼承方式對上一種組合繼承進行了優(yōu)化
function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }) const child = new Child(1) child.getValue() // 1 child instanceof Parent // true
以上繼承實現(xiàn)的核心就是將父類的原型賦值給了子類,并且將構(gòu)造函數(shù)設(shè)置為子類,這樣既解決了無用的父類屬性問題,還能正確的找到子類的構(gòu)造函數(shù)。
ES6中class 的繼承
ES6中引入了class關(guān)鍵字,class可以通過extends關(guān)鍵字實現(xiàn)繼承,還可以通過static關(guān)鍵字定義類的靜態(tài)方法,這比 ES5 的通過修改原型鏈實現(xiàn)繼承,要清晰和方便很多。需要注意的是,class關(guān)鍵字只是原型的語法糖,JavaScript繼承仍然是基于原型實現(xiàn)的。
class Parent { constructor(value) { this.val = value } getValue() { console.log(this.val) } } class Child extends Parent { constructor(value) { super(value) this.val = value } } let child = new Child(1) child.getValue() // 1 child instanceof Parent // true
class 實現(xiàn)繼承的核心在于使用 extends 表明繼承自哪個父類,并且在子類構(gòu)造函數(shù)中必須調(diào)用 super,因為這段代碼可以看成 Parent.call(this, value)。
五、DOM操作與BOM操作 1.DOM操作當(dāng)網(wǎng)頁被加載時,瀏覽器會創(chuàng)建頁面的文檔對象模型(DOM),我們可以認(rèn)為 DOM 就是 JS 能識別的 HTML 結(jié)構(gòu),一個普通的 JS 對象或者數(shù)組。接下來我們介紹常見DOM操作:
新增節(jié)點和移動節(jié)點
var div1 = document.getElementById("div1") // 添加新節(jié)點 var p1 = document.createElement("p") p1.innerHTML = "this is p1" div1.appendChild(p1) // 添加新創(chuàng)建的元素 // 移動已有節(jié)點。注意,這里是“移動”,并不是拷貝 var p2 = document.getElementById("p2") div1.appendChild(p2)
獲取父元素
var div1 = document.getElementById("div1") var parent = div1.parentElement
獲取子元素
var div1 = document.getElementById("div1") var child = div1.childNodes
刪除節(jié)點
var div1 = document.getElementById("div1") var child = div1.childNodes div1.removeChild(child[0])2.DOM事件模型和事件流
DOM事件模型分為捕獲和冒泡。一個事件發(fā)生后,會在子元素和父元素之間傳播(propagation)。這種傳播分成三個階段。
(1)捕獲階段:事件從window對象自上而下向目標(biāo)節(jié)點傳播的階段;
(2)目標(biāo)階段:真正的目標(biāo)節(jié)點正在處理事件的階段;
(3)冒泡階段:事件從目標(biāo)節(jié)點自下而上向window對象傳播的階段。
DOM事件捕獲的具體流程
捕獲是從上到下,事件先從window對象,然后再到document(對象),然后是html標(biāo)簽(通過document.documentElement獲取html標(biāo)簽),然后是body標(biāo)簽(通過document.body獲取body標(biāo)簽),然后按照普通的html結(jié)構(gòu)一層一層往下傳,最后到達目標(biāo)元素。
接下來我們看個事件冒泡的例子:
// 事件冒泡...... window.onclick = function() { console.log("window"); }; document.onclick = function() { console.log("document"); }; document.documentElement.onclick = function() { console.log("html"); }; document.body.onclick = function() { console.log("body"); } outer.onclick = function(ev) { console.log("outer"); }; inner.onclick = function(ev) { console.log("inner"); };
如何阻止冒泡?
通過event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執(zhí)行。
我們可以在上例中inner元素的click事件上,添加event.stopPropagation()這句話后,就阻止了父事件的執(zhí)行,最后只打印了"inner"。
inner.onclick = function(ev) { console.log("inner") ev.stopPropagation() }3.事件代理(事件委托)
由于事件會在冒泡階段向上傳播到父節(jié)點,因此可以把子節(jié)點的監(jiān)聽函數(shù)定義在父節(jié)點上,由父節(jié)點的監(jiān)聽函數(shù)統(tǒng)一處理多個子元素的事件。這種方法叫做事件的代理。
我們設(shè)定一種場景,如下代碼,一個 如果給每個標(biāo)簽一一都綁定一個事件,那對于內(nèi)存消耗是非常大的。借助事件代理,我們只需要給父容器div綁定方法即可,這樣不管點擊的是哪一個后代元素,都會根據(jù)冒泡傳播的傳遞機制,把父容器的click行為觸發(fā),然后把對應(yīng)的方法執(zhí)行,根據(jù)事件源,我們可以知道點擊的是誰,從而完成不同的事。 最后,使用代理的優(yōu)點如下: 使代碼簡潔 減少瀏覽器的內(nèi)存占用 BOM(瀏覽器對象模型)是瀏覽器本身的一些信息的設(shè)置和獲取,例如獲取瀏覽器的寬度、高度,設(shè)置讓瀏覽器跳轉(zhuǎn)到哪個地址。 window.screen對象:包含有關(guān)用戶屏幕的信息 window.location對象:用于獲得當(dāng)前頁面的地址(URL),并把瀏覽器重定向到新的頁面 window.history對象:瀏覽歷史的前進后退等 window.navigator對象:常常用來獲取瀏覽器信息、是否移動端訪問等等 獲取屏幕的寬度和高度 獲取網(wǎng)址、協(xié)議、path、參數(shù)、hash 等 另外,還有調(diào)用瀏覽器的前進、后退功能等 獲取瀏覽器特性(即俗稱的UA)然后識別客戶端,例如判斷是不是 Chrome 瀏覽器 Ajax 是一種異步請求數(shù)據(jù)的一種技術(shù),對于改善用戶的體驗和程序的性能很有幫助。 如何手寫 XMLHttpRequest 不借助任何庫 因為瀏覽器出于安全考慮,有同源策略。也就是說,如果協(xié)議、域名或者端口有一個不同就是跨域,Ajax 請求會失敗。 那么是出于什么安全考慮才會引入這種機制呢? 其實主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用用戶的登錄態(tài)發(fā)起惡意請求。 然后我們來考慮一個問題,請求跨域了,那么請求到底發(fā)出去沒有? 請求必然是發(fā)出去了,但是瀏覽器攔截了響應(yīng)。 常見的幾種跨域解決方案(具體如何實現(xiàn)詳見九種跨域方式實現(xiàn)原理(完整版)): JSONP:利用同源策略對 幾種常見模塊化規(guī)范的簡介(詳情請點擊前端模塊化詳解(完整版)): CommonJS規(guī)范主要用于服務(wù)端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因為同步意味著阻塞加載,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案 AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個模塊。不過,AMD規(guī)范開發(fā)成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。 CMD規(guī)范與AMD規(guī)范很相似,都用于瀏覽器編程,依賴就近,延遲執(zhí)行,可以很容易在Node.js中運行。不過,依賴SPM 打包,模塊的加載邏輯偏重 ES6 在語言標(biāo)準(zhǔn)的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當(dāng)簡單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案 歡迎關(guān)注公眾號:前端工匠,你的成長我們一起見證! 浪里行舟博客 前端面試之道 Web前端面試指導(dǎo) 前端入門4--DOM和BOM Web 前端面試指南與高頻考題解析 文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/54778.html
var div1 = document.getElementById("div1")
div1.addEventListener("click", function (e) {
// e.target 可以監(jiān)聽到觸發(fā)點擊事件的元素是哪一個
var target = e.target
if (e.nodeName === "A") {
// 點擊的是 元素
alert(target.innerHTML)
}
})
console.log(screen.width)
console.log(screen.height)
// 例如當(dāng)前網(wǎng)址是 https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.protocol) // https:
console.log(location.pathname) // /timeline/frontend
console.log(location.search) // ?a=10&b=10
console.log(location.hash) // #some
history.back()
history.forward()
var ua = navigator.userAgent
var isChrome = ua.indexOf("Chrome")
console.log(isChrome)
5.Ajax與跨域
簡單地說,在不需要重新刷新頁面的情況下,Ajax 通過異步請求加載后臺數(shù)據(jù),并在網(wǎng)頁上呈現(xiàn)出來。常見運用場景有表單驗證是否登入成功、百度搜索下拉框提示和快遞單號查詢等等。Ajax的目的是提高用戶體驗,較少網(wǎng)絡(luò)數(shù)據(jù)的傳輸量。var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
// 這里的函數(shù)異步執(zhí)行
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert(xhr.responseText)
}
}
}
xhr.open("GET", "/api", false)
xhr.send(null)
生命周期:localStorage 是持久化的本地存儲,存儲在其中的數(shù)據(jù)是永遠不會過期的,使其消失的唯一辦法是手動刪除;而 sessionStorage 是臨時性的本地存儲,它是會話級別的存儲,當(dāng)會話結(jié)束(頁面被關(guān)閉)時,存儲內(nèi)容也隨之被釋放。
參考資料
優(yōu)質(zhì)交流群
微信公眾號
摘要:當(dāng)前函數(shù)執(zhí)行完成后,當(dāng)前函數(shù)的執(zhí)行上下文出棧,并等待垃圾回收。作用域與作用域鏈到來有全局作用域函數(shù)作用域和塊級作用域新增。 引言 Javascript是前端面試的重點,本文重點梳理下 Javascript 中的??贾R點,然后就一些容易出現(xiàn)的題目進行解析。限于文章的篇幅,無法將知識點講解的面面俱到,本文只羅列了一些重難點,如果想要了解更多內(nèi)容歡迎點擊我的博客。 一、變量類型 1.JS ...
摘要:摸倚天魚文章推薦系列歡迎來到摸倚天魚系列以休閑態(tài)度推硬核文章探索亞文化摸魚倚分鐘問網(wǎng)癮少年今晚做些什么答作為一個平均每天上網(wǎng)小時的資深網(wǎng)癮少年真想把這個問題給撕了網(wǎng)癮少年每天看文章看影視聽音樂聽電臺白天若是摸魚晚上繼續(xù)摸魚難不成你以為網(wǎng)癮 摸倚天魚文章推薦系列 - 19/03/31 歡迎來到摸倚天魚系列, 以休閑態(tài)度, 推硬核文章, 探索亞文化~ 摸魚倚分鐘 問: 網(wǎng)癮少年今晚做些什么...
摘要:摸倚天魚文章推薦系列歡迎來到摸倚天魚系列以休閑態(tài)度推硬核文章探索亞文化摸魚倚分鐘問網(wǎng)癮少年今晚做些什么答作為一個平均每天上網(wǎng)小時的資深網(wǎng)癮少年真想把這個問題給撕了網(wǎng)癮少年每天看文章看影視聽音樂聽電臺白天若是摸魚晚上繼續(xù)摸魚難不成你以為網(wǎng)癮 摸倚天魚文章推薦系列 - 19/03/31 歡迎來到摸倚天魚系列, 以休閑態(tài)度, 推硬核文章, 探索亞文化~ 摸魚倚分鐘 問: 網(wǎng)癮少年今晚做些什么...
摘要:春招季如何橫掃面試核心考點基礎(chǔ)版前端面試之路二基礎(chǔ)整理的繼承和的繼承有什么區(qū)別的繼承時通過或構(gòu)造函數(shù)機制來實現(xiàn)。作用創(chuàng)建私有變量,減少全局變量,防止變量名污染。異步瀏覽器訪問服務(wù)器請求,用戶正常操作,瀏覽器后端進行請求。 春招季如何橫掃 Javascript 面試核心考點(基礎(chǔ)版)?前端面試之路二(javaScript基礎(chǔ)整理) ES5的繼承和ES6的繼承有什么區(qū)別 ES5的繼承時通過...
摘要:我在前面的文章中也提到了應(yīng)該怎么做自我介紹與項目介紹,詳情可以查看這篇文章備戰(zhàn)春招秋招系列初出茅廬的程序員該如何準(zhǔn)備面試。因此基于事件消息對象驅(qū)動的業(yè)務(wù)架構(gòu)可以是一系列流程。 showImg(https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c2ae52c?w=928&h=531&f=png&s=798562); 一 消息隊列MQ的...
閱讀 994·2021-11-08 13:22
閱讀 2936·2021-09-29 09:45
閱讀 2894·2021-09-09 11:52
閱讀 2323·2019-08-30 13:20
閱讀 3835·2019-08-29 13:28
閱讀 1437·2019-08-29 12:32
閱讀 2811·2019-08-29 11:10
閱讀 1701·2019-08-26 13:34