2020年已經到來,是否是該爲了更好的2020年再戰一回呢? ‘勝敗兵家事不期,包羞忍恥是男兒。江東子弟多才俊,捲土重來未可知’,那些在秋招失利的人,難道就心甘情願放棄嗎!javascript
此文總結2019年以來本人經歷以及瀏覽文章中,較熱門的一些面試題,涵蓋從CSS到JS再到Vue再到網絡等前端基礎到進階的一些知識。css
總結面試題涉及的知識點是對本身的一個提高,也但願能夠幫助到同窗們,在2020年會有一個更好的競爭能力。前端
css篇
- juejin.im/post/5e040e…Es6篇
- juejin.im/post/5e2962…Null是對象嗎?java
雖然typeof null
返回的是object
,但這實際上是JavaScript長久以來遺留的一個bug,null
實質上是基本數據類型的一種node
簡單數據類型與複雜數據類型在數據存儲上有什麼區別?jquery
簡單數據類型以棧的形式存儲,存儲的是值es6
複雜數據類型以堆的形式存儲,地址(指向堆中的值)存儲在棧中。面試
❗ 小知識: 當咱們把對象賦給另一個變量時,複製的是地址,指向同一塊內存空間,因此當其中一個對象改變時,另一個對象也會隨之改變ajax
JavaScript中原始類型的值被直接存儲在棧中,在定義變量時,棧就爲其分配好內存空間
- 存儲的值大小固定
- 空間較小
- 能夠直接操做其保存的變量,運行效率高
- 由系統自動分配存儲空間
複製代碼
JavaScript中引用類型(對象類型)的值實際存儲在堆內存中,
它在棧中只存儲了一個固定長度的地址,這個地址指向堆內存中的值
- 存儲的值大小不定,可動態調整
- 空間較大,運行效率低
- 沒法直接操做其內部存儲,使用其地址讀取值
- 經過代碼分配空間
複製代碼
- 可以正確判斷簡單數據類型(原始類型),除了null,typeof null結果爲object
- 對於對象而言,typeof不能正確判斷對象類型,typeof僅能夠區分開function,除此以外,結果均爲object
複製代碼
- 可以準確判斷複雜數據類型,可是不能正確判斷簡單數據類型
複製代碼
instanceof的原理正則表達式
instanceof是經過原型鏈進行判斷的,A instanceof B
,在A的原型鏈中層層查找,查找是否有原型等於B.prototype
,若是一直找到A的原型鏈的頂端,即Object.prototype.__proto__
,仍然不等於B.prototype
,那麼返回false,不然返回true
❗ 小知識:
var str = 'hello world' str instanceof String → false
var str = new String('hello world') str instanceof String → true
複製代碼
function test(person) {
person.age = 26;
person = {
name: 'foo',
age: 30
}
return person
}
const p1 = {
name: 'bar',
age: 25
}
const p2 = test(p1)
console.log(p1) // name:bar age:26
console.log(p2) // name:foo age:30
複製代碼
解析 首先函數傳參是按值傳遞的,即傳遞的是對象指針的副本,到函數內部修改參數這一步,p1的值也被修改,可是當咱們從新爲person分配一個對象時,是建立了一個新的地址(指針),也就和p1沒有關係了,因此最終2個變量的值不一樣
在條件判斷中,除了undefined、null、''、false、0、-0、NaN,其餘全部值都轉爲true,包括空數據和對象
複製代碼
對象在進行類型轉換時,會調用內部的[[ToPrimitive]]函數
- 若是已是原始類型,則不須要進行轉換
- 調用x.value(),若是轉換爲基礎類型,則返回轉換的值
- 調用x.toString(),若是轉換爲基礎類型,則返回轉換的值
- 若是都不返回原始類型值,則報錯
重寫:
let a = {
valueOf(){
// toDo
},
toString(){
// toDo
},
[Symbol.toPrimitive](){
// toDo
}
}
複製代碼
❗ 小知識:
引用類型 → Number
先進行valueOf,再進行toString
引用類型 → String
先進行toString,再進行valueOf
若valueOf和toString都不存在,則返回基本類型錯誤,拋出TypeError
例子:
const Obj1 = {
valueOf:() => {
console.log('valueOf')
return 123
},
toString:() => {
console.log('toString')
return 'Chicago'
}
}
const Obj2 = {
valueOf:() => {
console.log('valueOf')
return {}
},
toString:() => {
console.log('toString')
return '{}'
}
}
console.log(Obj1 - 1) → valueOf 122
console.log(Obj2 - 1) → valueOf toString TypeError
複製代碼
加法運算與其餘有所區別
- 當運算符其中一方爲字符串時,那麼另外一方也轉換爲字符串
- 當一側爲Number類型,另外一側爲原始類型,則將原始類型轉換爲Number
- 當一側爲Number類型,另外一側爲引用類型,則將引用類型和Number類型轉換爲字符串後拼接
例子:
1 + '1' → '11'
true + true → 2
4 + [1,2,3] → '41,2,3'
Ps: 特別注意 'a'+ +'b',由於 +'b' 會等於NaN,因此結果爲 'aNaN'
複製代碼
- NaN:和其餘類型比較永遠返回false(包括本身)
- Boolean:和其餘類型比較,Boolean首先被轉化爲Number(true → 一、false → 0)
- String和Number:String先轉化爲Number類型,再進行比較
- Null和undefined:null == undefined → true,除此以外,null、undefined和其餘任何值比較均爲false
- 原始類型和引用類型:引用類型轉換爲原始類型
複製代碼
1)因爲 ! 的優先級高於 == ,![] 首先會被轉換爲false,而後根據Boolean轉換原則,false將會轉換爲Number類型,即轉換爲 0,而後左側的 [] 轉換爲原始類型,也爲 0 ,因此最終結果爲 true
2)數組元素爲null、undefined時,該元素被看成空字符串,因此 [undefined]、[null] 都會變爲 0 , 最終 0 == false → true
包裝類型即 Boolean、Number、String
與原始類型的區別:
true === new Boolean → false
123 === new Number('123') → false
'Chicago' === new String('Chicago') → false
typeof new String('Chicago') → Object
typeof 'Chicago' → string
複製代碼
什麼是裝箱和拆箱? 裝箱即原始類型轉換爲包裝類型、拆箱即包裝類型轉換爲原始類型
如何使用原始類型來調用方法?
原始類型調用方法,實際上自動進行了裝箱和拆箱操做
var name1 = 'Chicago'
var name2 = name1.substring(2)
以上2行代碼,實際上發生了3個事情
- 建立一個String的包裝類實例
- 在實例上調用substring方法
- 銷燬實例
複製代碼
手動裝箱、拆箱
var name = new Number('123')
console.log(typeof name.valueOf()) → number
console.log(typeof name.toString()) → string
複製代碼
依據拆箱:
const a = {
value:[3,2,1],
valueOf:function(){
return this.value.pop()
} // 每次調用,刪除一個元素
}
console.log(a == 1 && a == 2 && a == 3) // true (注意僅能判斷一次)
複製代碼
誰調用它,this就指向誰這句話能夠說即精準又帶坑
(綁定方式) 影響this的指向實際有4種:
- 默認綁定:全局調用
- 隱式調用:對象調用
- 顯示調用:call()、apply()、bind()
- new綁定
複製代碼
function foo(){
console.log(this.a)
}
var a = 2
foo() // 2 → this指向全局
複製代碼
function foo(){
console.log(this.a)
}
var obj1 = {
a = 1,
foo
}
var obj2 = {
a = 2,
foo
}
obj1.foo() // 1 → this 指向 obj1
obj2.foo() // 2 → this 指向 obj2
複製代碼
function foo(){
console.log(this.a)
bar.apply( {a:2},arguments )
}
function bar(b){
console.log(this.a + b)
}
var a = 1 // 全局 a 變量
foo(3) // 1 5 → 1 說明第一個打印種 this 指向全局,5 說明第二個打印中 this 指向 {a:2}
複製代碼
❗ 小知識:
call()、apply()、bind()三者區別:
call()、apply()屬於當即執行函數,區別在於接收的參數形式不一樣,前者是一次傳入參數,後者參數能夠是數組
bind()則是建立一個新的包裝函數,而且返回,它不會當即執行bind(this,arg1,arg2···)
複製代碼
▲ 當call、apply、bind傳入的第一個參數爲 undefined/null 時,嚴格模式下this值爲傳入的undefined/null,非嚴格模式下,實際應用默認綁定,即指向全局(node環境下指向global、瀏覽器環境下指向window)
function info(){
console.log(this);
console.log(this.age);
}
var person = {
age:20,
info
}
var age = 28;
var info = person.info;
info.call(null); // window 、 28
複製代碼
- 構造函數返回值不是function/object
function Super(age){
this.age = age
}
let instance = new Super('26')
console.log(instance.age) // '26'
- 構造函數返回function/object
function Super(age){
this.age = age
let obj = {
a:2
}
return obj
}
let instance = new Super('26')
console.log(instance.age) // undefined → 返回的新obj中沒有age
複製代碼
靈魂拷問:new 的實現原理
1 - 建立一個新對象
2 - 這個新對象會被執行[[原型]]連接
3 - 屬性和方法被加入到this引用的對象裏,並執行構造函數中的方法
4 - 若是函數沒有返回其餘對象,那麼this指向這個新對象,不然this指向構造函數返回的對象
複製代碼
❗ 小知識:
對於this的綁定問題,優先級以下
New > 顯式綁定 > 隱式綁定 > 默認綁定
複製代碼
1) 箭頭函數沒有本身的this
當咱們使用箭頭函數的時候,箭頭函數會默認幫咱們綁定外層this的值,因此在箭頭函數中this的值與外層的this是同樣的
例子1:
const obj = {
a: () => {
console.log(this)
}
}
obj.a() //打出來的是window
由於箭頭函數默認不會使用本身的this,而是會和外層的this保持一致,最外層的this就是window對象
複製代碼
例子2:
let obj = {
age:20,
info:function(){
return () => {
console.log(this.age)
}
}
}
let person = { age:28 }
let info1 = obj.info()
info1() // 20
let info2 = obj.info.call(person)
info2() // 28
複製代碼
2) 箭頭函數不能在call方法修改裏面的this
函數的this能夠經過call等顯式綁定的方式修改,而爲了減小this的複雜性,箭頭函數沒法用call()來指定this
const obj = {
a: () => {
console.log(this)
}
}
obj.a.call('123') //打出來的結果依然是window對象
複製代碼
無論咱們給函數進行幾回bind顯式綁定,函數中的this永遠由 第一次bind 決定
let a = {}
let fn = function(){
console.log(this)
}
fn.bind().bind(a)() // => Window
複製代碼
紅寶書上對於閉包的定義:閉包是指有權訪問另一個函數做用域中的變量的函數
簡單來講,閉包就是一個函數A內部有另外一個函數B,函數B能夠訪問到函數A的變量和方法,此時函數B就是閉包
例子:
function A(){
let a = 1
window.B = function(){
console.log(a)
}
}
A() // 定義a,賦值window.B
B() // 1 → 訪問到函數A內部的變量
複製代碼
閉包存在乎義
在Js中,閉包存在的意義就是讓咱們能夠間接訪問到函數內部的變量 (函數內部變量的引用也會在內部函數中,不會由於執行完函數就被銷燬,但過多的閉包會形成內存泄漏)
function getOuter(){
var data = 'outer'
function getDate(str){
console.log(str + data) // 訪問外部變量 'data'
}
return getDate('I am')
}
getOuter() // I am outer
複製代碼
function getOuter(){
var date = '815';
function getDate(str){
console.log(str + date); //訪問外部的date
}
return getDate; //外部函數返回
}
var today = getOuter();
today('今天是:'); //"今天是:815"
today('明天不是:'); //"明天不是:815"
複製代碼
function updateCount(){
var count = 0;
function getCount(val){
count = val; // 更新外部函數變量
console.log(count);
}
return getCount; //外部函數返回
}
var count = updateCount();
count(815); //815
count(816); //816
複製代碼
for (var i = 0; i <= 5; i++) {
setTimeout(function () {
console.log(i)
}, 1000 * i)
} // 6,6,6,6,6,6
for(var i = 1;i <= 5; i++){
(function(j){
setTimeout( () => {
console.log(j)
},j * 1000)
})(i)
} // 0,1,2,3,4,5
// Tips:經過let定義i也可以解決,由於let具備塊級做用域
複製代碼
一、首先能夠經過Object.assign
來實現,Object.assgin
只會拷貝全部的屬性值到新的對象中,但若是屬性值是一個對象的話,拷貝的是地址,因此並非深拷貝
let obj = {
a: 1,
b:{
foo:'foo',
bar:'bar'
}
}
let objCopy = Object.assign({},obj)
console.log(objCopy) // {a:1,b:{foo:'foo',bar:'bar'}}
obj.a = 2
console.log(objCopy.a) // 1 → 不會隨着obj修改而修改
obj.b.foo = 'FOO'
console.log(objCopy.b) // {foo:'FOO',bar:'bar'} → 拷貝的是地址,指向同一個值,因此修改obj.b會影響到objCopy
複製代碼
二、也能夠經過展開運算符...來實現淺拷貝
let obj = {
a: 1
b:{
foo:'foo',
bar:'bar'
}
}
let objCopy = { ...obj }
console.log(objCopy) // {a:1,b:{foo:'foo',bar:'bar'}}
obj.a = 2
console.log(objCopy.a) // 1
obj.b.foo = 'FOO'
console.log(objCopy.b) // {foo:'FOO',bar:'bar'}
與Object.assign同樣,屬性值爲對象的拷貝,拷貝的是地址
複製代碼
一般來講淺拷貝能夠解決大部分的問題,但若是遇到下面這種狀況,就須要深拷貝來解決
let a = {
age:1,
jobs:{
first:'FE'
}
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native
複製代碼
淺拷貝只能解決第一層的問題,若是對象的屬性仍是對象的話,該屬性二者會共享相同的地址,假如咱們不想b的對象屬性隨a改變而改變,就須要經過深拷貝
1 - JSON.parse(JSON.stringify(object))
let a = {
age: 1,
jobs: { first: 'FE' }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE
複製代碼
2 - lodash庫中的cloneDeep()
複製代碼
1 - var【聲明變量】
var 沒有塊的概念,能夠跨塊訪問,沒法跨函數訪問
2 - let【聲明塊中的變量】
let 只能在塊做用域裏訪問,不能跨塊訪問,更不能跨函數訪問
3 - const【聲明常量,一旦賦值便不可修改】
const 只能在塊級做用域裏訪問,並且不能修改值
Tips: 這裏的不能修改,並非變量的值不能改動,而是變量所指向的那個內存地址保存的指針不能改動
複製代碼
❗ 小知識:
var a = 1
let b = 1
const c = 1
console.log(window.a) // 1
console.log(window.b) // undefined
console.log(window.c) // undefined
在全局做用域下使用let和const聲明變量,變量並不會被掛載到window上,這一點與var不一樣
關於const,還有兩個注意點:
- const聲明以後必須立刻賦值,不然報錯
- const簡單類型一旦聲明就不能修改,而複雜類型(數組,對象)指針指向的地址不能修改,但內部數據能夠修改
複製代碼
console.log(a) // undefined
var a = 1
複製代碼
上面兩行代碼,雖然在打印a前變量並無被聲明,可是卻可使用這個未聲明的變量,不報錯,這一種狀況就叫作提高,並且提高的是聲明
實際上,提高不只僅只能做用於變量的聲明,函數的聲明也會被提高
console.log(a) // f a(){}
function a(){}
var a = 1
複製代碼
函數的聲明優先級高於變量的聲明
console.log(a) // ReferenceError: Cannot access 'a' before initialization
let a
複製代碼
爲什麼此次就會報錯呢? 只要一進入當前做用域,所要使用得變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量,這就是暫時性死區
var a = 123; // 聲明
if (true) {
a = 'A'; // 報錯 由於本塊級做用域有a聲明變量
let a; // 綁定if這個塊級的做用域 不能出現a變量
}
複製代碼
對於暫時性死區,個人理解是聲明提高了,但初始化沒有被提高,而提高是聲明提高,並初始化爲undefined
函數提高優於變量提高,函數提高會把整個函數提高到做用域頂部,變量提高只會把聲明提高到做用域頂部
每個JavaScript對象(null除外)在建立的時候就會有一個與之關聯的對象,這個對象就是原型對象
每個對象都會從原型上繼承屬性
複製代碼
new
來建立一個對象的函數new
建立出來的對象,即是實例實例經過__proto__指向原型,經過constructor指向構造函數
複製代碼
以Object
爲例子,Object
即是一個構造函數,咱們經過它來構建實例
const instance = new Object()
複製代碼
這裏,instance
是Object
的實例,Object
是instance
的構造函數,構造函數擁有一個prototype
的屬性來指向原型,所以有
const prototype = Object.prototype
複製代碼
原型、構造函數、實例 三者關係
實例.__proto__ === 原型
原型.constructor === 構造函數
構造函數.prototype === 原型
Tips:
const instance = new Object()
instance.constructor === Object // true
當獲取 實例.constructor 時,其實實例上並無constructor屬性,當不能讀到constructor屬性時,會從實例的原型中讀取
則有 instance.constructor === instance.__proto__.constructor
若是修改了instance.__proto__,instance.constructor將再也不爲Object
instance.__proto__ = null
instance.constructor === Object // false
複製代碼
原型鏈是由相互關聯的原型對象組成的鏈狀結構
每一個對象都有__proto__
屬性(構造函數也有),指向建立該對象的構造函數的原型 ,__proto__
將對象連接起來組成了原型鏈。是一個能夠用來實現繼承和共享屬性的有限鏈
Object.prototype
(Object.prototype.__proto__ === null
),假如仍是沒有找到,則輸出undefinedprototype
屬性(b.prototype.B = 1
),但這樣修改會致使全部繼承於這個對象的實例的屬性發生改變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 instancof Parent // true
複製代碼
這種方式優勢在於構造函數能夠傳參,不會與父類共享引用屬性,能夠複用父類的函數,但缺點就是在繼承父類函數的時候調用父類構造函數,致使子類的原型上會多了不須要的父類屬性,存在內存浪費
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
}
}) // 將父類的原型賦值給子類,並將原型的constructor修改成子類
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
複製代碼
這種寄生組合繼承是對組合繼承進行優化的,核心就是將父類的原型賦值給子類,而且將構造函數設置爲子類,這樣解決了無用的父類屬性問題,還能正確的找到子類的構造函數
其實JavaScript中並不存在類的概念,class只是一種語法糖,本質來講仍是函數
class Person{}
Person instanceof Function // true
複製代碼
Class繼承
在ES6中,咱們能夠經過class
實現繼承
class Parent{
constructor(value){
this.val = value
}
getValue(){
console.log(this.val)
}
}
class Child extends Parent{
constructor(value){
super(value)
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
複製代碼
Class實現繼承,核心在於使用extends
關鍵字來代表繼承自哪一個父類,而且在子類構造函數中必須調用super
說到做用域,咱們要先理解什麼是執行上下文
執行上下文 能夠簡單理解爲一個對象,它具備三個類型:
eval()
執行上下文經過代碼執行過程來理解執行上下文
push
到執行棧頂層active EC
,而後開始執行函數中的代碼,全局執行上下文(caller)被掛起pop
移除出執行棧,控制權交回給全局執行上下文(caller),繼續按照自上而下的順序執行代碼❗ 小知識:
變量對象,是執行上下文中的一部分,能夠抽象爲一種數據做用域
其實也能夠理解爲一個簡單的對象,存儲着該執行上下文中的全部變量和函數聲明(不包括函數表達式)
活動對象(AO)- 當變量對象所處的上下文被激活時(active EC)時,稱爲活動對象
複製代碼
做用域能夠理解爲當前上下文中聲明的變量和函數的做用範圍,它規定了如何查找變量,也就是當前執行代碼對變量的訪問權限
做用域能夠分爲 塊級做用域 和 函數做用域
做用域特性:
let a = function(){
console.log(1)
}
(function a(){
a = 'a'
console.log(a)
})() // ƒ a() { a = 'a' ; console.log(a) }
複製代碼
做用域鏈能夠理解爲一組對象列表,由當前環境與上層環境的一系列變量對象組成,所以咱們能夠經過做用域鏈訪問到父級裏面聲明的變量或者函數
做用域鏈由兩部分組成
[[scope]]
屬性:指向父級變量對象和做用域鏈,也就是包含了父級的[[scope]]
和活動變量(AO)
因爲[[scope]]
包含父級[[scope]]
造成鏈狀關係,便自上而下造成鏈式做用域
做用域鏈做用
保證當前執行環境裏,有權訪問的變量和函數是有序的(做用域鏈的變量只能向上訪問變量,訪問到window對象時被終止)
Ps:做用域鏈不容許向下訪問
做用域鏈和原型繼承查找時的區別 - 若是去查找一個普通對象的屬性,可是在當前對象和其原型中都找不到時,會返回undefined
;但查找的屬性在做用域鏈中不存在的話就會拋出ReferenceError
<script>
標籤引入<script>
標籤<script defer>
延遲加載,元素解析完畢後執行<script async>
異步加載,但執行時會阻塞元素渲染typeof null // 'Object'
null 是一個空對象,沒有任何的屬性和方法
typeof undefined // 'undefined'
undefined 是一個表示'無'的原始值或表示缺乏值,例如變量被聲明瞭,但沒有任何賦值時
複製代碼
從內存來看null和undefined,本質區別是什麼?
內存泄漏 在使用一些內存以後,若是後面再也不須要用到這些內存,但沒有將它們及時釋放掉,就稱爲內存泄漏
若是出現嚴重的內存泄漏,那麼有可能使得內存愈來愈大,最終致使瀏覽器崩潰
未定義的變量會在全局對象建立一個新變量
function foo(arg){
bar = 'I am belong to global' // 未定義,會建立在全局中
}
解決方案:
- 在JavaScript頭部加上`use strict`,使用嚴格模式避免意外的全局變量
複製代碼
定義定時器(setTimeout / setInterval)後沒有移除(clearTimeout / clearInterval)
複製代碼
閉包的關鍵是匿名函數能夠訪問父級做用域的變量,讓變量不被回收
若是不及時清除,就會致使內存泄漏
複製代碼
經過GC垃圾回收機制來解決內存泄漏
所謂垃圾回收機制,是指找到內存空間中的垃圾並回收,能夠再次利用這部份內存空間
垃圾回收機制有兩種方式:
在JavaScript
中,咱們能夠採用多種方式實現循環
while
do...while
for
for...in
for...of
- 遍歷對象及其原型鏈上可枚舉的屬性
- 若是用於遍歷數組,除了遍歷其元素外,還會遍歷數組對象自定義的可枚舉屬性及其原型鏈上的可枚舉屬性
- 遍歷對象返回的屬性名和遍歷數組返回的索引都是字符串索引
- 某些狀況下,可能按隨機順序遍歷數組元素
複製代碼
- es6 中添加的循環遍歷語法
- 支持遍歷數組,類數組對象(DOM NodeList),字符串,Map 對象,Set 對象
- 不支持遍歷普通對象
- 遍歷後輸出的結果爲數組元素的值
- 可搭配實例方法 entries(),同時輸出數組的內容和索引
複製代碼
- 返回對象自身可枚舉屬性組成的數組
- 不會遍歷對象原型鏈上的屬性以及 Symbol 屬性
- 對數組的遍歷順序和 for in 一致
複製代碼
Tips: for in
更適合遍歷對象,儘可能不用for in
來遍歷數組
迭代相關
對每一項運行給定函數,全true則返回true
複製代碼
對數組中每一項運行函數,返回該函數會返回true項
複製代碼
對數組每一項運行函數,沒有返回值 (forEach沒法中途跳出forEach循環,break、continue和return都不奏效。)
複製代碼
對每一項運行函數,返回每次函數調用的結果組成的數組
複製代碼
對每一項運行函數,若是對任一項返回了true,則返回true
複製代碼
其餘
經過指定鏈接符生成字符串
複製代碼
數組尾部推入和彈出,改變原數組,返回操做項
複製代碼
數組頭部彈出和推入,改變原數組,返回操做項
複製代碼
數組排序(fn定義排序規則)與反轉,改變原數組
複製代碼
鏈接數組,不改變原數組,返回新數組(淺拷貝)
複製代碼
截斷數組,返回截斷後的新數組,不改變原數組
複製代碼
從下標start開始,刪除number個元素,並插入arg,返回所刪除元素組成的數組,改變原數組
複製代碼
查找數組元素,返回下標索引
複製代碼
歸併數組,prev爲累計值,cur爲當前值,defaultPrev爲初始值
複製代碼
鏈接字符串
複製代碼
檢索字符串、從後向前檢索字符串
複製代碼
找到一個或多個正則表達式的匹配
替換與正則表達式匹配的子串
檢索與正則表達式匹配的值
複製代碼
截取字符串片斷,並在新的字符串中返回被截取的片斷
複製代碼
從起始索引號提取字符串中指定數目的字符
複製代碼
截取字符串中兩個指定的索引號之間的字符。
複製代碼
用於把一個字符串經過指定的分隔符進行分隔成數組
複製代碼
返回字符串
複製代碼
返回某個字符串對象的原始值
複製代碼
做用:生成一個數組,遍歷原數組,將每個元素拿出來作一些變化後存入新數組
[1,2,3].map(item => item + 1) // [2,3,4]
另外map的回調接收三個參數,分別是當前元素、索引,原數組
常見題:['1','2','3'].map(parseInt) 結果是什麼?
['1','2','3'].map(parseInt) → [1,NaN,NaN]
解析:
第一輪遍歷 parseInt('1',0) // 1
第二輪遍歷 parseInt('2',1) // NaN
第三輪遍歷 parseInt('3',2) // NaN
複製代碼
做用:生成一個新數組,在遍歷數組的時候將返回值爲true的元素放在新數組
場景:咱們能夠利用這個函數刪除一些不須要的元素(過濾)
let array = [1,2,3,4,5]
let newArray = array.filter(item => item !== 5)
console.log(newArray) // [1,2,3,4]
Tips:與map一致,也接收3個參數
複製代碼
做用:將數組中的元素經過回調函數最後轉換爲一個值(歸併)
場景:實現一個將數組裏的元素所有相加獲得一個值的功能
const arr = [1,2,3]
const sum = arr.reduce((acc,current) => {
return acc + current
},0)
console.log(sum) // 6
對於reduce來講,它只接受2個參數,分別是回調函數和初始值:
- 首先初始值爲0,該值會在執行第一次回調函數時做爲第一個參數傳入
- 回調函數接收四個參數,分別爲累計值、當前元素、當前索引、原數組
- 在第一次執行回調時,當前值和初始值相加爲1,該結果會做爲第二次回調時的累計值(第一個參數)傳入
- 第二次執行時,相加的值分別爲1和2,以此類推,循環結果後得出最終值
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
- total 必選,初始值或計算結束後的返回值
- currentValue 必選,當前元素
- currentIndex 可選,當前索引
- arr 可選,當前元素所屬的數組對象
- initialValue 可選,傳遞給函數的初始值
複製代碼
mixin
extend
call/apply
二者區別在於加載方式不一樣、規範不一樣
require('./a')() // 假設a模塊是一個函數,當即執行a模塊函數
var data = reuqire('./a').data // 假設a模塊導出一個對象
Tips:require寫在代碼哪一行均可以
import Jq from 'jquery'
import * as _ from '_'
import {a,b,c} from './a'
import {default as alias, a as a_a, b, c} from './a';
Tips:import應該用在代碼行開頭
複製代碼
❗ 小知識:
require特色:
- 提供了服務器/瀏覽器的模塊加載方案。非語言層面的標準。
- 只能在運行時肯定模塊的依賴關係及輸入/輸出的遍歷,沒法進行靜態優化
import特色:
- 語言規格層面支持模塊功能。
- 支持編譯時靜態分析,動態綁定
複製代碼
NaN與任務值比較都是false,包括他本身,判斷一個變量爲NaN,能夠經過isNaN()
isNaN(NaN) // true
複製代碼
- 消除JavaScript語法的一些不合理、不嚴謹之處,減小一些怪異行爲
- 消除代碼運行的一些不安全行爲,保證代碼運行的安全
- 提升編譯器效率,增長運行速度
- 爲將來新版本的JavaScript作好鋪墊
複製代碼
- 嚴格模式下,delete運算符後跟隨非法標識符,會拋出語法錯誤
- 嚴格模式下,定義同名屬性會拋出語法錯誤
- 嚴格模式下,函數形參存在同名,會拋出語法錯誤
- 嚴格模式下,不容許八進制整數直接量
- 嚴格模式下,arguments對象是傳入函數內實參列表的靜態副本
- 嚴格模式下,eval和arguments當作關鍵字,它們不能被賦值和用做變量聲明
- 嚴格模式下,變量必須先聲明,直接給變量賦值,不會隱式建立全局變量,不能用with
- 嚴格模式下,call/apply第一個參數爲null/undefined,不會被轉換爲window
複製代碼
JavaScript
中的變量是鬆散類型,所謂鬆散類型,就是指當一個變量被聲明出來就能夠保存任何類型的值,一個變量所保存值的類型也能夠改變
若是事件處理函數(click)調用頻率無限制,會加劇瀏覽器的負擔,致使用戶體驗很是糟糕,那麼咱們能夠採用debounce(防抖) 和 throttle(節流) 的方式來減小調用頻率,同時又不影響實際效果
例如:
🔺當持續觸發scroll事件時,事件處理函數handle只在中止滾動1000毫秒以後纔會調用一次,即持續觸發滾動事件的過程當中,handle一直沒有執行
複製代碼
function debounce(handle){
let timeout = null // 建立一個標記用來存放定時器
return function(){
clearTimeout(timeout) // 每當用戶調用的時候把前一個定時器清空
timeout = setTimeout(() => {
handle.apply(this,arguments)
},500) // 500ms後觸發,期間再次調用,則從新計算延時
}
}
function sayHi(){
console.log('防抖成功')
}
var btn = document.getElementById('button')
btn.addEventListener('click',debounce(sayHi))
複製代碼
function throttle(handle){
let canRun = true // 經過閉包保存一個標記,不被回收
return function(){
if(!canRun) return // 在函數頭部判斷標記是否爲true,爲false時不容許調用handle
canRun = false // 設置標記爲false
setTimeout(() => { // 將外部傳入的函數的執行放在setTimeout中
fn.apply(this, arguments)
// 最後在setTimeout執行完畢後再把標記設置爲true(關鍵)表示能夠執行下一次循環了。當定時器沒有執行的時候標記永遠是false,在開頭被return掉
canRun = true
}, 500);
}
}
function sayHi() {
console.log('節流成功');
}
var btn = document.getElementById('button');
btn.addEventListener('click', throttle(sayHi)); // 節流
複製代碼
哪些事件不支持冒泡?
- 鼠標事件:mouseleave、mouseenter
- 焦點事件:blur、focus
- UI事件:scroll、resize
- ···
複製代碼
對於同一個事件,監聽捕獲和冒泡,分別對應相應的處理函數,監聽到捕獲事件時,先暫停執行,直到冒泡事件被捕獲後再執行事件
非IE瀏覽器:event.stopPropagation()
IE瀏覽器:window.event.cancelBubble = true
function stopBubble(e){
// 若是提供了事件對象,則是非IE瀏覽器下
if(e && e.stopPropagation){
// 所以它支持W3C的stopPropagation()方法
e.stopPropagation()
}else{
// IE瀏覽器下,取消事件冒泡
window.event.cancelBubble = true
}
}
複製代碼
非IE瀏覽器:event.preventDefault()
IE瀏覽器:window.event.returnValue = false
function stopDefault(e) {
//阻止默認瀏覽器動做(W3C)
if (e && e.preventDefault) e.preventDefault();
//IE中阻止函數器默認動做的方式
else window.event.returnValue = false;
return false;
}
複製代碼
所謂事件委託,就是利用事件冒泡的原理,讓本身所觸發的事件,讓其父元素代替執行
即:不在事件(直接DOM)上設置監聽函數,而是在其父元素上設置監聽函數,經過事件冒泡,父元素能夠監聽到子元素上事件的觸發,經過判斷事件發生在哪個子元素上來作出不一樣的響應
<ul>
<li>蘋果</li>
<li>香蕉</li>
<li>鳳梨</li>
</ul>
// 在ul上設置監聽函數(Good)
document.querySelector('ul').onclick = (event) => {
let target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// 在每個li上監聽函數(Bad)
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
複製代碼
- 事件冒泡:父元素下不管是什麼元素,點擊後都會觸發 box 的點擊事件
- 事件委託:能夠對父元素下的元素進行篩選
複製代碼
高階函數(Highter-order-function)的定義很簡單,就是至少知足下列一個條件的函數:
也就是說高階函數是對其餘函數進行操做的函數,能夠將它們做爲參數傳遞,或者是返回它們。
Array.prototype.map
、Array.prototype.filter
、Array.prototype.reduce
···,它們接受一個函數做爲參數,並應用這個函數到列表的每個元素對比使用高階函數和不使用高階函數
例子:有一個數組[1,2,3,4],咱們想要生成一個新數組,其元素是以前數組的兩倍
- 不使用高階函數
const arr1 = [1,2,3,4]
const arr2 = []
for(let i = 0; i < arr1.length; i++){
arr2.push(arr1[i] * 2)
}
- 使用高階函數
const arr1 = [1,2,3,4]
const arr2 = []
arr2 = arr1.map( item => item * 2)
複製代碼
在判斷類型的時候能夠同個Object.prototype.toString.call來獲取對應對象返回的字符串,如:
let isString = obj => Object.prototype.toString.call( obj ) === '[object String]'
let isArray = obj => Object.prototype.toString.call( obj ) === '[object Array]'
let isNumber = obj => Object.prototype.toString.call( obj ) === '[object Number]'
能夠發現這三行有許多重複代碼,只須要把具體的類型抽離出來就能夠封裝成一個判斷類型的方法,如:
let isType = (type, obj) => {
return Object.prototype.toString.call(obj) === '[Object ' + type + ']'
}
isType('String')('123'); // true
isType('Array')([1, 2, 3]); // true
isType('Number')(123); // true
複製代碼
柯里化 - 簡單來講就是隻傳遞函數一部分參數來調用它,讓它返回一個新函數去處理剩下的參數。
經過add()函數來了解柯里化
const add = (...args) => args.reduce( (a, b) => a + b ) // a爲初始值或計算結束的返回值,b爲當前元素
// 傳入多個參數,執行add函數
add(1,2) // 3
// 假設咱們實現了一個柯里化函數,支持一次傳入一個參數
let sum = currying(add)
// 封裝第一個參數,方便重用
let addCurryOne = sum(1)
addCurryOne(2) // 3
addCurryOne(3) // 4
複製代碼
currying
函數咱們能夠理解所謂的柯里化函數,就是封裝一系列的處理步驟,經過閉包將參數集中起來計算,最後再把須要處理的參數傳進去,那麼如何實現currying
函數呢?
實現原理就是用閉包把傳入的參數保存起來,當傳入參數的數量足夠執行函數時,就開始執行函數
實現一個健壯的currying函數:
function currying(fn, length) {
length = length || fn.length // 第一次調用獲取函數 fn 參數的長度,後續調用獲取 fn 剩餘參數的長度
return function( ...args ) { // currying 包裹以後返回一個新函數,接收參數爲 ...args
return args.length >= length // 新函數接收的參數長度是否大於等於 fn 剩餘參數須要接收的長度
? fn.apply(this, args) // 知足要求,執行 fn 函數,傳入新函數的參數
: currying(fn.bind(this, ...agrs), length - args.length) // 不知足要求,遞歸 currying 函數,新的 fn 爲 bind 返回的新函數(bind 綁定了 ...args 參數,未執行),新的 length 爲 fn 剩餘參數的長度
}
}
// Test
const fn = currying(function(a,b,c) {
console.log([a, b, c])
})
fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]
複製代碼
延遲計算:
const add = (...args) => args.reduce((a, b) => a + b);
// 簡化寫法
function currying(func) {
const args = [];
return function result(...rest) {
if (rest.length === 0) {
return func(...args);
} else {
args.push(...rest);
return result;
}
}
}
const sum = currying(add);
sum(1,2)(3); // 未真正求值
sum(4); // 未真正求值
sum(); // 輸出 10
複製代碼
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b}
複製代碼
解析:
array.from - 從一個類數組或可迭代對象建立一個新的,淺拷貝的數組實例
array.flat - 用於將嵌套的數組'拉平'(扁平化)
[1,2,[3,4]].flat() // [1,2,3,4]
array.sort - 用於對數組的元素進行排序
- 該函數要比較兩個值,而後返回一個用於說明這兩個值的相對順序的數字。
比較函數應該具備兩個參數 a 和 b,其返回值以下:
- 若 a 小於 b,在排序後的數組中 a 應該出如今 b 以前,則返回一個小於 0 的值。
- 若 a 等於 b,則返回 0
- 若 a 大於 b,則返回一個大於 0 的值。
1 - arr.flat(Infinity) 先將數組扁平化
2 - new Set(arr.flat(Infinity)) 去重扁平化後的數組
3 - Array.from(new Set(arr.flat(Infinity))) 建立一個新的,淺拷貝的數組實例
4 - Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b } 將該新數組進行升序排序
複製代碼
createDocumentFragment() - 建立一個DOM片斷
createElement() - 建立一個具體的元素
createTextNode() - 建立一個文本節點
複製代碼
appendChild() - 添加子節點
removeChild() - 移除子節點
replaceChild() - 替換子節點
insertBefore() - 插入
複製代碼
getElementsByTagName() - 經過標籤名稱
getElementsByName() - 經過元素的Name屬性
getElementById() - 經過元素id,具備惟一性
複製代碼
Javascript
是一種解釋型語言,C、C++
等語言先編譯後執行,而Javascript
是在程序的運行過程當中逐行進行解釋Javascript
是一種基於對象的腳本語言,它不只能夠建立對象,也能使用現有的對象Javascript
語言中採用的是弱類型的變量類型,對使用的數據類型未作出嚴格的要求,是基於Java
基本語句和控制的腳本語言Javascript
是一種採用事件驅動的腳本語言,它不須要通過Web服務器就能夠對用戶的輸入作出響應Javascript
不依賴於操做系統,僅須要瀏覽器的支持/**
* 兼容低版本IE,element爲須要綁定事件的元素,
* eventName爲事件名(保持addEventListener語法,去掉on),fun爲事件響應函數
*/
function addEvent(element, eventName, fun){
if(element.addEventListener){
element.addEventListener(eventName, fun, false)
}else{
ele.attachEvent('on' + eventName, fun)
}
}
複製代碼
sort()內部是利用遞歸進行冒泡排序的
- 比較相鄰的元素,若是第一個比第二個大,就交換它們兩個
- 對每一對相鄰元素作一樣的工做,從開始第一對到結尾的最後一對,在這一點,最後的元素應該會是最大的數
- 針對全部的元素重複以上的步驟,除了最後一個
- 持續每次對愈來愈少的元素重複上面的步驟,知道沒有任何一對數字須要比較
複製代碼
var arr = [1,5,4,2]
sort()的比較邏輯爲:
- 1和5比,1和4比,1和2比
- 5和4比,5和2比
- 4和2比
複製代碼
- return > 0 則交換數組相鄰2個元素的位置
- arr.sort(function(a,b){ ... })
- a → 表明每一次執行匿名函數時,找到數組中的當前項
- b → 表明當前項的後一項
複製代碼
1 - 升序
var arr = [45, 42, 10, 147, 7, 65, -74]
console.log(arr.sort()) → 默認法 缺點:默認排序順序是根據字符串UniCode碼。由於排序是按照字符串UniCode碼的順序進行排序的(按首位排序)
// [-74, 10, 147, 42, 45, 65, 7]
console.log(
arr.sort(function(a, b) {
return a - b; // 若return返回值大於0(即a>b),則a,b交換位置
}) → 指定排序規則,return可返回任何值
)
// [-74, 7, 10, 42, 45, 65, 147]
2 - 降序
var arr = [45, 42, 10, 111, 7, 65, -74];
console.log(
arr.sort(function(a, b) {
return b - a; // 若return返回值大於零(即b>a),則a,b交換位置
}) → 指定排序規則,return可返回任何值
);
複製代碼
經過判斷Global對象是否爲window,若是不爲window,則當前腳本沒有運行在瀏覽器中
複製代碼
var a = [1, 2, 3, 5];
alert(Math.max.apply(null, a)); //最大值
alert(Math.min.apply(null, a)); //最小值
之因此須要用到apply,是由於 Math.max / Math.min 不支持傳遞數組過去
複製代碼
offsetWidth → 返回元素的寬度(包括元素寬度、內邊距和邊框,不包括外邊距)
offsetHeight → 返回元素的高度(包括元素高度、內邊距和邊框,不包括外邊距)
clientWidth → 返回元素的寬度(包括元素寬度、內邊距,不包括邊框和外邊距)
clientHeight → 返回元素的高度(包括元素高度、內邊距,不包括邊框和外邊距)
style.width → 返回元素的寬度(包括元素寬度,不包括內邊距、邊框和外邊距)
style.height → 返回元素的高度(包括元素高度,不包括內邊距、邊框和外邊距)
scrollWidth → 返回元素的寬度(包括元素寬度、內邊距和溢出尺寸,不包括邊框和外邊距),無溢出的狀況,與clientWidth相同
scrollHeigh → 返回元素的高度(包括元素高度、內邊距和溢出尺寸,不包括邊框和外邊距),無溢出的狀況,與clientHeight相同
offsetTop → 返回元素的上外緣距離最近採用定位父元素內壁的距離,若是父元素中沒有采用定位的,則是獲取上外邊緣距離文檔內壁的距離。
offsetLeft → 此屬性和offsetTop的原理是同樣的,只不過方位不一樣
scrollLeft → 此屬性能夠獲取或者設置對象的最左邊到對象在當前窗口顯示的範圍內的左邊的距離,也就是元素被滾動條向左拉動的距離。
scrollTop → 此屬性能夠獲取或者設置對象的最頂部到對象在當前窗口顯示的範圍內的頂邊的距離,也就是元素滾動條被向下拉動的距離。
在函數調用的時候,瀏覽器每次都會傳遞進兩個隱式參數,一個是函數的上下文對象this
,另一個則是封裝實參的僞數組對象arguments
關於arguments
arguments
定義是對象,可是由於對象的屬性是無序的,而arguments
是用來存儲實參的,是有順序的,它具備和數組相同的訪問性質及方式,並擁有數組長度屬性length
(類數組對象、用來存儲實際傳遞給函數的參數)arguments
訪問單個參數的方式與訪問數組元素的方式相同,例如arguments[0]
、arguments[1]
、arguments[n]
,在函數中不須要明確指出參數名,就能訪問它們。經過length
屬性能夠知道實參的個數。arguments
有一個callee
屬性,返回正被執行的Function
對象function fun() {
console.log(arguments.callee === fun); // true
}
fun();
複製代碼
arguments
對象是容許在運行時進行修改function fun() {
arguments[0] = 'sex';
console.log(arguments[0]); // sex
}
fun('name', 'age');
複製代碼
一行代碼實現僞數組arguments轉換爲數組
var args = [].slice.call(arguments)
複製代碼
Javascript
做爲主要運行在瀏覽器的腳本語言,主要用途之一就是操做Dom
若是Javascript
同時有兩個線程,同時對同一個Dom
進行操做,這時瀏覽器應該聽哪一個線程的,又如何判斷優先級?
爲了不這種問題,Javascript
必須是一門單線程語言
因爲Javascript
是單線程語言,當遇到異步任務(如Ajax
)時,不可能一直等到異步執行完成後,再繼續往下執行,由於這段時間瀏覽器處於空閒狀態,會致使巨大的資源浪費
當執行某個函數、事件(指定過回調函數)時,就會進入執行棧中,等待主線程讀取
執行棧可視化:
主線程與執行棧不一樣,主線程規定了如今執行執行棧中的哪一個事件
主線程循環: 即主線程會不停的從執行棧中獲取事件,而後執行完全部棧中的同步代碼
當遇到一個異步事件後,並不會一直等待異步事件返回結果,而是會將這個事件掛在與執行棧不一樣的隊列中,這個隊列稱爲任務隊列TaskQueue
當主線程將執行棧中的全部代碼都執行完後,主線程將會去查看任務隊列中是否存在任務。 若是存在,那麼主線程會依次執行那些任務隊列中的回調函數
Javascript
異步執行的運行機制TaskQueue
)。只要異步任務有了返回結果,就在任務隊列之中放置一個事件異步任務能夠分爲兩類,不一樣類型的API註冊的任務會依次進入到各自對應的隊列中,而後等待事件循環(EventLoop
)將它們依次壓入執行棧中執行
MacroTask
script(總體代碼),setTimeout,setInterval,setImmediate,UI渲染,I/O流操做,postMessage,MessageChannel
複製代碼
MicroTask
Promise,MutaionObserver,process.nextTick
複製代碼
Event Loop(事件循環)中,每一次循環稱爲 tick, 每一次tick的任務以下:
綜上所述
宏任務 → 全部微任務 → 下一個宏任務
複製代碼
題 1:
setTimeout(function () {
console.log(1)
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val)
})
console.log(4)
複製代碼
Result:
2 → 4 → 3 → 1
複製代碼
題 2:
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => {
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
複製代碼
Result:
4 → 3 → 2 → 1
複製代碼
解析:
- script任務運行,首先遇到Promise實例,執行構造函數,輸出4,此時微任務隊列中有t2和t1
- script任務繼續執行同步代碼,輸出3後第一個宏任務執行完成
- 執行全部的微任務,即輸出2和1
??? 爲何t2會比t1先執行 ???
- 根據 Promises/A+ 規範
- Promise.resolve 方法容許調用時不帶參數,直接返回一個resolved 狀態的 Promise 對象
- 當即 resolved 的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時
複製代碼
Javascript
是單線程工做,也就是隻有一個腳本執行完以後才能夠執行下一個腳本,兩個腳本不能同時執行,那麼若是腳本耗時很長,後面的腳本都必須排隊等待,會拖延整個程序的執行
異步編程的六種方式
function f1(cb){
setTimeout(() => {
console.log('f1')
})
cb()
}
function f2(){
console.log('f2')
}
f1(f2) // f2 → f1
複製代碼
function fn(a,b,cb){
var num = Math.ceil(Math.random() * (a - b) + b)
cb(num)
}
fn(10,20,function(num){
console.log("隨機數" + num);
}) // 10 ~ 20 的隨機數
複製代碼
總結:
- 回調函數易於實現,便於理解,可是屢次回調會致使代碼高度耦合
- 回調函數定義:函數A做爲參數(函數引用)傳遞到另一個函數B,而且這個函數B執行函數A,咱們就叫函數A叫作回調函數,若是沒有名稱(函數表達式),咱們就叫它匿名回調函數
- 回調函數優勢:簡單,容易理解
- 回調函數缺點:不利於代碼的閱讀和維護,各部分之間高度耦合,並且每個任務只能指定一個回調函數
複製代碼
監聽函數有:on、bind、listen、addEventListener、observe
複製代碼
優勢:容易理解,能夠綁定多個事件,每個事件能夠接收多個回調函數,並且能夠減小耦合,利於模塊化
缺點:整個程序都要變成事件驅動型,運行流程會變得不清晰
複製代碼
element.onclick = function(){
// toDo
}
Or:
element.onclick = handler1
element.onclick = handler2
element.onclick = handler3
缺點:
當同一個element元素綁定多個事件時,只有最後一個事件會被添加,上述只有handler3會被添加執行
複製代碼
elment.attachEvent("onclick", handler1)
elment.attachEvent("onclick", handler2)
elment.attachEvent("onclick", handler3)
Result: 3 → 2 → 1
elment.addEventListener("click", handler1, false)
elment.addEventListener("click", handler2, false)
elment.addEventListener("click", handler3, false)
Result:1 → 2 → 3
(PS:該方法的第三個參數是泡沫獲取,是一個布爾值:當爲false時表示由裏向外,true表示由外向裏。)
複製代碼
DOM - addEventListener()和removeListener()
addEventListenner()和removeListenner()表示用來分配和刪除事件的函數。這兩種方法都須要三種參數,分別爲:
- 事件名稱(String)
- 觸發事件的回調函數(function)
- 指定事件的處理函數的時期或階段(boolean)
複製代碼
定義了一種一對多的關係,讓多個觀察者同時監聽某一個主題對象,這一個主題對象一旦發生狀態變化,就會通知全部觀察者對象,使得它們可以自動更新本身
優勢:
- 支持簡單的廣播通訊,自動通知全部已經訂閱過的對象
- 頁面載入後,目標對象很容易與觀察者存在一種動態關聯,增長靈活性
- 目標對象與觀察者之間的抽象耦合關係可以單獨擴展以及重用
複製代碼
- promise對象是commonJS工做組提出的一種規範,一種模式,目的是爲了異步編程提供統一接口
- promise是一種模式,promise能夠幫忙管理異步方式返回的代碼。他將代碼進行封裝並添加一個相似於事件處理的管理層。咱們可使用promise來註冊代碼,這些代碼會在在promise成功或者失敗後運行
- promise完成以後,對應的代碼也會執行。咱們能夠註冊任意數量的函數再成功或者失敗後運行,也能夠在任什麼時候候註冊事件處理程序
- promise有兩種狀態:一、等待(pending);二、完成(settled)
- promise會一直處於等待狀態,直到它所包裝的異步調用返回/超時/結束
- 這時候promise狀態變成完成。完成狀態分紅兩類:一、解決(resolved);二、拒絕(rejected)
- promise解決(resolved):意味着順利結束。promise拒絕(rejected)意味着沒有順利結束
複製代碼
Javascript
只能與同一個域中的頁面進行通信
兩個腳本被認爲是同源的條件:
XMLHttpRequest
對象實現的AJAX請求那樣受到同源策略的限制,jsonP能夠實現跨越同源策略XMLHttpRequest
或ActiveX
的支持callback
的方式回傳結果。將回調方法的權限給了調用方。GET
請求而不支持POST
等其餘類型的HTTP請求AJAX
是一種建立交互式網頁應用的網頁開發技術AJAX
能夠實如今沒必要刷新整個頁面的狀況下實現局部更新,與服務器進行異步通信的技術XMLHttpRequest
對象XMLHttpRequest
對象能夠說是AJAX
的核心對象,是一種支持異步請求的技術。即XMLHttpRequest
使你可使用javascript
向服務器提出請求並作出響應,又不會致使阻塞用戶。經過XMLHttpRequest
對象,能夠實如今不刷新整個頁面的狀況下實現局部更新
XMLHttpRequest
對象的常見屬性onreadystatechange
- 一個Js函數對象,當readyState屬性改變時會調用它(請求狀態改變的事件觸發器)readyState
- Http請求的狀態,當一個XMLHttpRequest
初次建立時,這個屬性的值從0開始,直到接收到完整的Http響應,這個值增長到4
XMLHttpRequest
對象已建立或已被abort()
方法重置。open()
方法已調用,可是send()
方法未調用。請求尚未被髮送send()
方法已調用,HTTP 請求已發送到 Web 服務器,但未接收到響應readyState 的值不會遞減,除非當一個請求在處理過程當中的時候調用了 abort() 或 open() 方法
每次這個屬性的值增長的時候,都會觸發 onreadystatechange 事件句柄。
複製代碼
status
- 由服務器返回的 HTTP 狀態代碼,如 200 表示成功,而 404 表示 "Not Found" 錯誤。當 readyState 小於 3 的時候讀取這一屬性會致使一個異常。關於Http狀態碼,常見以下:
1) 1XX 通知
2) 2XX 成功
3) 3XX 重定向
4) 4XX 客戶端錯誤
5) 5XX 服務端錯誤
最基本的響應狀態:
- 200('ok') : 服務器已成功處理了請求
- 400('bad request'):服務器沒法解析該請求
- 500('Internal Server Error'):服務器內部錯誤服務器遇到錯誤,沒法完成請求
- 301('Moved Permanently'):永久移動請求的網頁已永久移動到新位置,即永久重定向
- 404('Not Found'):未找到服務器找不到請求的網頁
- 409('Conflict'):服務器在完成請求時發生衝突
複製代碼
XMLHttpRequest
對象的常見APIOpen()
- 建立http請求
url
在一個已經激活的request下(已經調用open()或者openRequest()方法的請求)再次調用這個方法至關於調用了abort()方法。
複製代碼
setRequestHeader()
- 向一個打開但未發送的請求設置或添加一個Http請求(設置請求頭)
send()
- 發送http請求,使用傳遞給open()方法的參數,以及傳遞給該方法的可選請求體
abort()
- 取消當前響應getAllResponseHeaders()
- 把Http響應頭部做爲未解析的字符串返回getResponseHeader()
- 返回指定的 HTTP 響應頭部的值
XMLHttpRequest
對象Http
對象Http
請求的請求頭var ajax = {}
// 兼容性建立httpRequest
ajax.httpRequest = function(){
// 判斷是否支持XMLHttpRequest
if(window.XMLHttpRequest){
return new XMLHttpRequest()
}
// 兼容 Ie
var versions = [
"MSXML2.XmlHttp.6.0",
"MSXML2.XmlHttp.5.0",
"MSXML2.XmlHttp.4.0",
"MSXML2.XmlHttp.3.0",
"MSXML2.XmlHttp.2.0",
"Microsoft.XmlHttp"
]
// 定義局部xhr,存儲Ie瀏覽器的ActiveXObject對象
var xhr
for (var i = 0; i < versions.length; i++) {
try {
xhr = new ActiveXObject(versions[i]);
break;
} catch (e) {
}
}
return xhr
}
ajax.send = function(url, callback, method, data, async){
// 默認異步
if(async === undefined){
async = true
}
var httpRequest = ajax.httpRequest()
// 建立Http請求(open)
httpRequest.open(method, url, async)
// 請求狀態改變的事件觸發器
httpRequest.onreadystatechange = function(){
// readyState變爲4時,從服務器拿到數據
if(httpRequest.readyState === 4){
callback(httpRequest.responseText)
}
}
// 設置http請求的請求頭(setRequestHeader)
if (method == 'POST') {
//給指定的HTTP請求頭賦值
httpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
}
// 發送Http請求
httpRequest.send(data)
}
// 封裝GET/POST請求
ajax.get = function (url, data, callback, async) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, 'GET', null, async)
}
ajax.post = function (url, data, callback, async) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
ajax.send(url, callback, 'POST', query.join('&'), async)
}
複製代碼
Access-Control-Allow-Origin
)<script>
標籤(只能解決GET請求)document.Ready()
能夠定義多個函數,按照綁定的順序進行執行,而onload
只能執行一次,定義多個onload
,後面的會覆蓋前面的onload()
是在頁面全部元素都加載完後才執行document.Ready()
是在DOM繪製完就執行,沒必要等到徹底加載完再執行</body>
前寫Js則是運行到就開始執行,無論有沒有加載完成,因此有可能出現Js操做節點時獲取不到該節點Object.freeze
適用於對象值,它可以讓對象不可變(該對象屬性不能修改)let foo = {
a:'A'
}
let bar = {
b:'B'
}
Object.freeze(foo)
foo.a = 'a'
console.log(foo) // {a: "A"}
複製代碼
相比const
,二者是不一樣的概念,const
做用是聲明變量(一個只讀變量),一旦聲明,這個變量就不能修改,而Object.freeze()
做用是讓一個對象不可變
new
new
- 配合構造函數建立對象function Person(name, age, job){
this.name = name
this.age = age
this.job = job
}
var person = new Person('CHICAGO', 21, 'itMan')
複製代碼
new
在建立對象的過程當中作了哪4件事function Person(){
this.name = 'CHICAGO'
}
new Person()
- 建立一個空對象 → var obj = {}
- 將空對象賦給this → this = obj
- 將空對象的 __proto__ 屬性指向構造函數的 prototype → this.__proto__ = Person().prototype
- 返回這個對象(this)→ return this
複製代碼
{}
this
值__proto__
指向構造函數的prototype
return
語句,則返回this
hasOwnProperty()
- 返回一個布爾值,判斷對象是否包含特定的自身(非繼承)屬性判斷自身屬性是否存在
Object.prototype.c= 'C';
var obj = new Object()
obj.a = 'A'
function changeObj(){
obj.b = 'B'
delete obj.a
}
obj.hasOwnProperty('a') // true
obj.hasOwnProperty('c') // false
changeObj()
obj.hasOwnProperty('a') // false
obj.hasOwnProperty('b') // false
複製代碼
若是在函數原型上定義一個變量,hasOwnProperty()方法會直接忽略掉
in
運算符若是指定的屬性在指定的對象或其原型鏈中,則in
運算符返回true
Object.prototype.c= 'C';
var obj = new Object()
obj.a = 'A'
console.log('a' in obj) // true
console.log('c' in obj) // true
複製代碼
in
運算符會檢查它或者其原型鏈是否包含具備指定名稱的屬性
Object.create()
能夠實現建立沒有原型的對象const objHavePrototype = {}
console.log(objHavePrototype.toString()) // [Object object]
const objHaveNoPrototype = Object.create(null)
console.log(objHaveNoPrototype.toString) // TypeError: objHaveNoPrototype.toString is not a function
typeof objHaveNoPrototype // object
複製代碼
咱們知道 typeof null === 'object'
,但 null 並無 prototype 屬性
event.preventDefault()
event.defaultPrevented
屬性,該屬性返回一個布爾值用於區分是否在特定元素中使用了event.preventDefault()
undefined
,有時倒是報錯var foo = {}
console.log(foo.a) // undefined
console.log(foo.a.A) // TypeError: Cannot read property 'A' of undefined
複製代碼
觀察上面這個例子,有人會認爲都是返回undefined
或者都是報錯,當咱們訪問foo.a
的時候,因爲foo對象並不存在a屬性,因此返回的是undefined,而當咱們去訪問一個undefined的屬性時,就會報出TypeError: Cannot read property 'XXX' of undefined
的錯誤
因爲計算機是經過二進制來存儲東西,那麼0.1
在二進制中會表示爲
// (0011) 表示循環
0.1 = 2^-4 * 1.10011(0011)
複製代碼
能夠發現,0.1
在二進制中是一個無限循環的數字,並非精確的0.1
,其實不少十進制小數用二進制表示都會是無限循環的,由於Javascript
採用浮點數標準,致使會裁剪掉咱們的數字,那麼這些循環的數字被裁剪以後,就會出現精度丟失的問題,也就形成0.1
再也不是0.1
,而是變成0.100000000000000002
0.100000000000000002 === 0.1 // true
複製代碼
天然,0.2
在二進制中也是無限循環,因此
0.1 + 0.2 === 0.30000000000000004 // true
複製代碼
0.1 + 0.2 != 0.3
parseFloat(str)
- 解析一個字符串,並返回一個浮點數
toFixed(num)
- 把Number四捨五入爲指定小數位數的數字
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true
複製代碼
Javascript
內容較多,本章列舉了較爲重要的部分,我的會繼續總結知識,對該章持續更新,後續會總結Js
重點手寫題,建議對本文進行收藏ES6
核心知識點