前端面試之JavaScript(一)

Ilion發表於2019-04-09

也到了自己快找工作的時候了,所以最近在複習前端的知識,這裡是自己對JavaScript的一些知識點的總結,以後會持續更新,分享自己的複習知識,有些是在不懂的時候參考大佬的講解。有些理解不到位的地方還請指正。祝自己找工作順利!!!

1、bind、call、apply

這三個函式都會改變this的指向,call和apply更適用於在函式執行時改變this;而bind會返回一個新的函式,新函式的this由bind傳入的引數決定,所以bind更適用於返回一個新函式,這個函式在將來才會執行,比如DOM新增事件。

// call
Function.prototype.myCall = function (ctx = window, ...arg) {
  if (typeof this !== "function") return
  ctx.fn = this
  let res = ctx.fn(...arg)
  delete ctx.fn
  return res
}
// apply 
Function.prototype.myApply = function (ctx = window, arg) {
  if (typeof this !== "function") return
  ctx.fn = this
  if(!Array.isArray(arg)) {
    throw new Error('需要陣列')
  }
  let res = ctx.fn(...arg)
  delete ctx.fn
  return res
}
// bind
Function.prototype.newbBind = function(target){
    target = target || window
    var self = this;
    // 這裡的arguments是在呼叫時傳入的引數
    var args = [].slice.call(arguments, 1);
    var temp = function () {}

    function f(){
        // 這裡的arguments是bind返回的新函式傳入的參
        var _args = [].slice.call(arguments,0)//將一個類陣列轉化為陣列

        return self.apply(this instanceof temp? this : target, args.concat(_args))
    }
    temp.prototype = self.prototype
    f.prototype = new temp()
    return f
}
複製程式碼

2、函式柯里化

在Lambda演算(一套數理邏輯的形式系統,具體我也沒深入研究過)中有個小技巧:假如一個函式只能收一個引數,那麼這個函式怎麼實現加法呢,因為高階函式是可以當引數傳遞和返回值的,所以問題就簡化為:寫一個只有一個引數的函式,而這個函式返回一個帶引數的函式,這樣就實現了能寫兩個引數的函式了——這就是所謂的柯里化(Currying,以邏輯學家Hsakell Curry命名),也可以理解為一種在處理函式過程中的邏輯思維方式。

在電腦科學中,柯里化(Currying)是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數且返回結果的新函式的技術。

function curry(fn, args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}

function multiFn(a, b, c) {
    return a * b * c;
}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);

// 參考:https://juejin.im/post/5c9c3989e51d454e3a3902b6

複製程式碼

3、原型、原型鏈

1、原型

原型是function的一個屬性,該屬性本質上是一個物件,它定義了建構函式構造出來的共有祖先,建構函式產生的例項物件可以繼承該屬性的方法和屬性,當例項訪問某個屬性找不到就會順著原型鏈訪問該屬性。

2、原型鏈

有了原型,原型還是一個物件,那麼這個名為原型的物件自然還有自己的原型,這樣的原型上還有原型的結構就構成了原型鏈。

原型鏈是是描述例項物件與建構函式的原型之間的關係,如果例項物件找不到某個屬性或者方法就會到建構函式的prototype上查詢,如果還是找不到就會訪問建構函式prototype屬性的__proto__屬性,直到null。

4、繼承

在JavaScript中沒有類的概念,傳統語言中類通過拷貝實現繼承,JavaScript通過原型鏈、原型委託的方式實現繼承。

組合繼承:

function Father() {
    this.name = "father"
}
Father.prototype.say = function() {
    console.log('say')
}
function Child() {
    Father.call(this)
    this.age = 12
}
Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child
複製程式碼

聖盃模式

let inhert = (function() {
    function F(){}
    return function(father, child){
        F.prototype = father.prototype
        child.prototype = new F()
        child.prototype.constructor = child
    }
})
複製程式碼

5、this

參考:juejin.im/post/5c96d0…

1、什麼是this?

this是JavaScript中的一個關鍵字,被自動定義在所有函式的作用域中。**this是在執行的時候進行繫結,並不是在編寫的時候進行繫結,它的上下文取決於函式呼叫時的各種條件。**this的繫結和函式的宣告位置無關,只取決於函式的呼叫方式。

當一個函式被呼叫的時候,會建立一個活動記錄(也成為執行上下文)。這個記錄會包含函式在哪裡呼叫(呼叫棧)、函式呼叫的方法、傳入的引數等資訊。this就是記錄中的一個屬性,會在函式執行的過程中用到。

2、呼叫位置

呼叫位置指的是函式被調呼叫的位置而不是宣告的位置。

3、繫結規則

1、預設繫結

預設繫結的時候this指向window,預設繫結是指函式不帶任何修飾的函式引用進行呼叫。比如:

function foo() {
    console.log(this)
}
foo() // window
複製程式碼

但是需要注意的是在嚴格模式下,預設繫結並不會指向window

2、隱式繫結

隱式繫結通常以物件作為執行上下文呼叫。但是我們需要明白一個道理:不管是在物件中宣告一個函式,還是先定義再新增函式的引用,嚴格來叔這個函式都不屬於該物件。

隱式繫結規則會把函式呼叫中的this繫結到這個上下文物件,因為呼叫foo的時候this被繫結到該物件,因此this.a等同於obj.a。

物件屬性引用鏈中只有最後一層會影響呼叫的位置。

let obj2 = {
  a:2,
  foo1:foo1
}
let obj1 = {
  a:1,
  obj2:obj2
}
function foo1() {
  console.log(this.a)
}
obj1.obj2.foo1() // 2
複製程式碼
1、隱式繫結丟失
var a = 'window'
let obj = {
  a: 'obj',
  foo() {
    console.log(this.a)
  }
}
let bar = obj.foo
bar()
複製程式碼

因為bar是obj.foo的一個引用,但是實際上引用的是foo函式的本身,因此bar()是一個不帶任何修飾符的呼叫所以是預設繫結,this指向window。

var a = 'window'
let obj = {
  a: 'obj',
  foo() {
    console.log(this.a)
  }
}
function doFoo(fn) {
  fn()
}
doFoo(obj.foo)
複製程式碼

這裡呼叫doFoo的時候參入了obj.foo作為實參,並將obj.foo賦值給fn,所以fn是foo函式的引用,在呼叫fn的時候也是不帶任何修飾的呼叫,所以是預設呼叫this指向window。

以下這種情況this也是指向window。原因和上面一樣。

var a = 'window'
let obj = {
  a: 'obj',
  foo() {
    console.log(this.a)
  }
}
setTimeout(obj.foo, 1000)
複製程式碼

所以上面我們可以看出回撥函式丟失this是非常常見的。

3、顯示繫結

  • call
  • apply
  • bind:bind會返回一個硬編碼的新函式,它會把引數設定為this的上下文並呼叫原始函式。

如果把null或者undefined作為this繫結的物件傳入其中,這些值會被忽略,實際上是預設繫結。

4、new繫結

5、繫結優先順序

new > call、apply、bind > 隱式繫結 > 預設繫結

6、防抖節流

節流(throttle)是防止使用者頻繁操作,造成瀏覽器效能消耗過大

防抖(debounce),就是指觸發事件後在 n 秒內函式只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函式執行時間。

// 節流函式(throttle)
function throttle (fn, wait=500) {
    let pre_time = 0
    return function(...arg) {
        let curr_time = Date.now()
        if(curr_time - pre_time > wait) {
            fn.apply(this, arg)
            pre_time = curr_time
        }
    }
}
// 防抖函式(debounce)
function debounce(fn, wait = 500, immediately = true) {
    let timer
    return function(...arg) {
        if(immediately) {
            fn.apply(this, arg)
            immediately = false
        }
        clearTimout(timer)
        timer = setTimout(()=> {
            fn.apply(this, arg)
        }, wait)
    }
}
複製程式碼

7、Promise

面試常見問題:

1、瞭解 Promise 嗎?

2、Promise 解決的痛點是什麼?

3、Promise 解決的痛點還有其他方法可以解決嗎?如果有,請列舉。

4、Promise 如何使用?

5、Promise 常用的方法有哪些?它們的作用是什麼?如何使用?

6、Promise 在事件迴圈中的執行過程是怎樣的?

7、Promise 的業界實現都有哪些?

8、能不能手寫一個 Promise ?

function myPromise(constructor){
    let self=this;
    self.status="pending" //定義狀態改變前的初始狀態
    self.value=undefined;//定義狀態為resolved的時候的狀態
    self.reason=undefined;//定義狀態為rejected的時候的狀態
    function resolve(value){
        //兩個==="pending",保證了狀態的改變是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //兩個==="pending",保證了狀態的改變是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕獲構造異常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}
myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}

// 來源:https://github.com/forthealllight/blog/issues/4
複製程式碼

1、promise含義

promise是非同步程式設計的一種解決方案,解決了回撥地獄的問題。Promise是一個容器儲存著某個未來才會結束的事件的結果,也可以說是一個物件從它可以獲取非同步操作的訊息

特點:

  1. 物件狀態不受外界影響,只有pending(進行中)、fulfilled(已成功)和rejected(已失敗)三種狀態。
  2. 一旦狀態改變就不會再改變。只能從pending(進行中)到fulfilled(已成功)或pending(進行中)到reject(以失敗)。

2、基本語法

const promise = new Promise(function(resolve, reject) {
    if(/*success*/) {
        resolve(val)
    } else {
        reject(val)
    }
})
複製程式碼

Promise接受一個函式作為引數,該函式接受兩個引數,它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。

resolve的作用是在非同步操作成功的時候呼叫,並將非同步操作的結果作為引數傳遞出去;reject是在非同步操作失敗的時候呼叫。

Promise例項生成之後可以用then方法分別指定成功和失敗的回撥函式

promise.then(function() {
    /*success*/
}, function() {
    /*failure*/
})
複製程式碼

第一個引數是成功時呼叫,第二個是失敗時呼叫,這兩個函式都接受Promise物件傳出的值作為引數,第一個成功時的回撥函式時必須的失敗時的回撥函式不是必須的。

resolve函式的引數除了正常的值以外,還可能是另一個 Promise 例項,比如像下面這樣。

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})


const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail
// 這裡p2的狀態決定p1的狀態,p2後的then都是針對p1的
複製程式碼

這裡p2的狀態決定p1的狀態,p2後的then都是針對p1的

Promise的具體例子:

function timeout(ms) {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, ms, 'done');
  })
}
let p = timeout(100).then((val) => {
  console.log(val)
})
複製程式碼

Promise建立之後會立即執行

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// promise
// Hi!
// resolved.
複製程式碼

實現Ajax

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出錯了', error);
});
複製程式碼

3、promise.prototype.then

then方法是定義在原型物件Promise.prototype上的,它的作用是為 Promise 例項新增狀態改變時的回撥函式。前面說過,then方法的第一個引數是resolved狀態的回撥函式,第二個引數(可選)是rejected狀態的回撥函式。

then方法也可以返回一個新的Promise例項,因此可以採用鏈式呼叫:

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function funcA(comments) {
  console.log("resolved: ", comments);
}, function funcB(err){
  console.log("rejected: ", err);
});
複製程式碼

4、promise.prototype.catch

用於錯誤的捕獲

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });
複製程式碼

上面程式碼中,第二種寫法要好於第一種寫法,理由是**第二種(catch)寫法可以捕獲前面then方法執行中的錯誤,**也更接近同步的寫法(try/catch)。因此,建議總是使用catch方法,而不使用then方法的第二個引數。

5、promise.prototype.finally

finally方法用於執行不管最後狀態如何,都會執行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製程式碼

6、promise.all()

該方法用於將多個Promise例項包裝成一個新的Promise例項。

const p = Promise.all([p1, p2, p3])
複製程式碼

Promise.all()接受一個陣列,陣列的值都是Promise物件,如果不是則會呼叫Promise.resolve()方法。(Promise.all方法的引數可以不是陣列,但必須具有 Iterator 介面,且返回的每個成員都是 Promise 例項。)

p的狀態由p1p2p3決定,分成兩種情況。

(1)只有p1p2p3的狀態都變成fulfilledp的狀態才會變成fulfilled,此時p1p2p3的返回值組成一個陣列,傳遞給p的回撥函式。

(2)只要p1p2p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的例項的返回值,會傳遞給p的回撥函式。

注意,如果作為引數的 Promise 例項,自己定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()catch方法。

7、promise.race()

Promise.race方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項。

const p = Promise.race([p1, p2, p3])
複製程式碼

上面程式碼中,只要p1p2p3之中有一個例項率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給p的回撥函式。和Promise一樣,陣列的值必須是promise物件,如果不是則會呼叫Promise.resolve()方法。

8、Promise.resolve()

Promise.resolve()方法可以將現有的物件轉換為Promise物件。

Promise.resolve('foo')
// 等同於
new Promise(function(resolve) {
    resolve('foo')
})
複製程式碼

9、Promise.reject()

Promise.reject(reason)方法也會返回一個新的 Promise 例項,該例項的狀態為rejected

let p = Promise.reject('foo')
// 等同於
new Promise((resolve, reject) => reject('foo'))
複製程式碼

8、深拷貝

因為陣列和物件都是引用值,所以當我們直接使用=賦值,會是兩個物件的指標指向同一個空間,當我們改變其中一個值的時候,另一個物件也會受到影響。當我們使用深拷貝重新開闢了一個記憶體空間,將該物件的指標指向新開闢的空間。

針對陣列我們可以使用[...arr]

針對物件我們可以使用Object.assign({}, obj)、{...obj}

以上兩種都是淺拷貝

也可以使用JSON.parse(JOSN.stringify(obj))

function deepClone(obj) {
    let res
    if(typeof obj === "object") {
        res = obj.constructor = Array?[]:{}
        for(let i in obj) {
            res[i] = typeof obj[i] === "object"?deepClone(obj[i]):obj[i]
        }
    } else {
        res = obj
    }
    return obj
}
複製程式碼

9、JavaScript事件迴圈

juejin.im/post/5bac87…

JavaScript將任務分為同步任務和非同步任務,在第一次執行的時候會將整個script程式碼看作巨集任務同步任務進入主執行緒,非同步任務進入Event Table註冊,當滿足條件非同步任務的回撥函式加入到Event Queue佇列中,當主執行緒空閒的時候,會從Event Queue取出對應的函式。巨集任務(script、setTimeout)和微任務(Promise、process.nextTick)分別進入不同的Event Table,它們的執行順序不一樣,當主執行緒空閒的時候首先會清空微任務佇列,然後再拿出一個巨集任務佇列的函式,然後再檢查微任務佇列,如此迴圈。

10、作用域、作用域鏈、執行上下文、預編譯

1、作用域

作用域是在執行時程式碼中的特定變數的有效範圍。作用域決定了程式碼區塊中變數和其他資源的可見性。作用域內層可以看見作用域外層,作用域外層不能看見作用域外層,所以作用域在不同作用域中宣告的變數不會造成汙染和命名衝突。

  • 全域性作用域

定義在最外層的函式和變數,未經宣告就賦值的變數,window的屬性。這裡需要注意的是var宣告的全域性變數以及未經宣告就賦值的變數會掛載到window屬性上,但是var宣告的變數不能刪除,未經宣告的變數可以刪除。

  • 函式作用域

當函式執行的時候就會在內部建立一個函式作用域,當函式執行完成就會銷燬該作用域。

  • 塊級作用域

在ES6之前是沒有塊級作用域的,ES6引入了let、const關鍵字就可以建立塊級作用域。

2、作用域鏈

當在一個函式內部搜尋一個變數的時候,如果該函式沒有宣告該變數,那麼就會順著程式碼執行環境建立的作用域逐層向外搜尋,一直搜尋到全域性作用域。

3、執行上下文

解釋階段:

  • 詞法分析
  • 語法分析
  • 作用域規則確定()

執行階段

  • 建立執行上下文
  • 執行函式程式碼
  • 垃圾回收

JavaScript在解釋階段便會確定作用域規則,但是執行上下文是在函式執行的前一刻。

執行上下文最明顯的就是this指向是在執行的時候確定的。

區別:執行上下文在執行時確定,隨時可以改變;作用域在定義時就確定,並且不會改變。同一作用域下,不同的呼叫會產生不同的執行上下文,從而產生不同的結果。

4、預編譯

  1. 建立AO物件
  2. 尋找形參和變數宣告
  3. 形參實參相統一
  4. 找函式宣告,函式名作為屬性名,函式體作為屬性值

5、閉包

當函式可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函式是在當前詞法作用域外執行。簡單講,閉包就是指有權訪問另一個函式作用域中的變數的函式。

建立閉包:

  1. 在函式內部引用外部函式
let a = 1
function foo() {
  console.log(a)
}
function bar() {
  let a = 2
  foo()
}
bar() // 1
複製程式碼
  1. 在函式內部返回函式
let a = 'window'
function foo() {
    let a = 'foo'
    return function() {
        console.log(a)
    }
}
let bar = foo()
bar() // foo
複製程式碼

閉包的應用和缺陷

  1. 設計私有的方法和變數。
  2. 容易造成記憶體洩露。

11、DOM事件

1、DOM事件級別

  1. DOM0:dom.onclick = function(){}
  2. DOM2:dom.addEventListenner('click',function(){})
  3. DOM3:dom.addEventListenner('keyup',fuction(){}),增加了事件型別

2、DOM事件模型

dom.addEventListenner('keyup',fuction(){}, false|true),第三個引數為false表示在冒泡階段觸發,第三個引數為true表示在捕獲階段觸發。先捕獲後冒泡。

  1. 捕獲:父元素到子元素,dom.addEventListenner('keyup',fuction(){}, true)
  2. 冒泡:子元素到父元素,dom.addEventListenner('keyup',fuction(){}, false)

事件委託就是基於事件冒泡的,當子元素觸發點選事件會冒泡到父元素,然後通過e.target來判斷子元素。

3、DOM事件流

通過冒泡或者捕獲怎麼到達目標物件的階段

事件首先通過捕獲到達目標元素,再通過目標元素冒泡到window物件,即先捕獲後冒泡

4、Event常見物件

  • 阻止預設行為:e.preventDefault()
  • 阻止冒泡:e.stopPropagation()
  • 阻止其他繫結的事件的執行(事件響應優先順序):e.stopImmediatePropagation()
  • e.target
  • e.currentTarget

5、自定義事件

參考:www.jianshu.com/p/71bb3cf19…

// 1.第一種
// 定義
let eve = new Event('coustome')
// 繫結
dom.addEventListenner('coustome', function(){})
// 觸發
dom.dispatch(eve)

// 2.第二種,可以新增資料
let eve1 = new CustomoeEvent('coustome', {data})
複製程式碼

12、new

  1. 建立一個新物件
  2. 將該物件的__proto__屬性指向函式的prototype屬性
  3. 將this指向該物件
  4. 如果該函式沒有顯示的返回物件則返回建立的物件
function New(fn, ...arg) {
    let res = {}
    if(fn.prototype !== null) {
        res = Object.create(fn.prototype)
    }
    let ret = fn.apply(res, arg)
    if(ret === "object" || ret === "function" && ret !== null) {
        return ret
    }
    retrun res
}
複製程式碼

13、JavaScript資料型別

  • object:包括Function、Date、Array等
  • number:數值,NaN和自身不相等,但是可以通過Object.is()來判斷
  • string:字串
  • boolean:布林
  • null:原型鏈的終點
  • undefined:表示變數宣告還沒有被賦值
  • symbol:ES6新增,表示獨一無二的值

1、隱式轉換

2、顯示轉換

3、包裝類

語法:let str = new String('hello world')

當我們宣告一個字串變數的時候let str1 = 'hello',這是字面量的形式,並且是一個不可變的值。我們訪問str1.length屬性、或其他屬性的時候,就會把該變數轉換成為一個String物件(這裡通常叫做包裝類),因為宣告的字串沒有該屬性,只有轉換為包裝類才有。在JavaScript中會把字串字面量轉化成String物件

4、null、undefined比較

null在數值轉換時被轉換為0,undefined會被轉換為NaN

  • nudefined

undefined只有一個值,即undefined。以下情況會出現undefined:

  1. 定義變數,但是沒有初始化;
  2. 呼叫某個函式時,實參個數小於形參個數時,未實參化的形參在函式呼叫過程中的值是undefined;
  3. 訪問物件沒有的屬性
  4. 函式預設的返回值
  5. 為初始化的變數執行typeof
  6. 未宣告的變數執行typeof
  • null

null也只有一個值,但是當我們執行typeof null的時候,會返回object。我們可以理解為null是一個空指標物件,還沒有儲存物件。以下幾種情況會使用出現null:

  1. 手動設定為null,比如在釋放變數的時候
  2. 未獲取到DOM節點
  3. 原型鏈頂端
  4. 在正則捕獲的時候,如果沒有捕獲到結果,預設也是null

5、判斷資料型別

  • typeof

不能區別null、物件、陣列、正規表示式等

  • instanceof

是基於原型鏈操作的:A instanceof B,判斷A的原型鏈上有沒有B的原型

  • Object.prototype.toString.call()

比較好的方法,但是IE6/7/8中 Object.prototype.toString.apply(null)返回“[object Object]”。

  • constructor

14、物件

1、語法

物件宣告可以使用字面量形式和建構函式形式

let obj = {}
let obj1 = new Object()
複製程式碼

這兩種方法生成的物件是一樣的,區別在於字面量形式可以新增多個鍵值對、建構函式形式只能逐個新增。

2、內建物件

JavaScript還有一些物件子型別,通常被稱為內建物件。

  • String
  • Number
  • Boolean
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

3、內容

物件中的值通常不會儲存在物件內部,通常情況下,儲存在物件容器內部的是這些屬性的名稱,它們就像指標一樣,指向這些值的真正儲存位置

1、訪問物件值的方法

let obj = {
    a:1
}
obj.a
obj['a']
複製程式碼

obj.a,的語法被稱為屬性訪問,obj['a']的方法被稱為鍵訪問,它們在大都數情況下是可以互換的,區別在於.a要符合命名的規範性,['a']可以接受任意的UTF-8/Unicode字元作為屬性名。比如"super-Fun!",這時候就不可以使用屬性訪問了。

注意:在物件中屬性名永遠都是字串,如果不是者會被轉換為字串。

2、可計算的屬性名

let a = "foo"
let obj = {
  [a + '1']: 'hello',
  [a + '2']: 'hello2'
}
複製程式碼

4、物件常見的方法

1、屬性描述符

1、檢視屬性描述符:Object.getOwnPropertyDescriptor(obj, props)

語法:

let myObj = {
  a: 1
}
console.log(Object.getOwnPropertyDescriptor(myObj, 'a'))
複製程式碼
  • value,屬性值
  • writable,是否可修改
  • configurable,是否可配置,如果為true則可以通過Object.defineProperty(obj, props)方法,來修改這些屬性,所以需要注意的是把configurable修改為false是一個單向操作無法撤銷。除了無法修改,configurable還會禁止刪除該屬性。
  • enumerable,是否可列舉

2、設定屬性描述符:Object.defineProperty(obj, props)

語法:

Object.defineProperty(myObj, 'b', {
  value:2,
  writable: false,
  configurable: true,
  enumerable: true
})
複製程式碼

所以我們通過設定writable,configurable為false來設定一個物件常量。

2、不變性

1、通過設定writable,configurable為false來設定一個物件常量。

2、禁止擴充:Object.preventzectensions(obj)

語法:

let myObj1 = {
  a: 1
}
Object.preventExtensions(myObj1)
myObj1.b = 2
myObj1.b // undefined
複製程式碼

3、密封:Object.seal(obj)

實際上這個方法會呼叫Object.preventzectensions(obj)方法,並將現有屬性的configurable設為false,所以密封之後既不能新增新的屬性,也不能刪除和配置現有屬性

4、凍結:Object.freeze(obj)

這個方法會呼叫Object.seal()方法,並將現有屬性的writable設為false,故既不能新增新的屬性,也不能刪除、配置、修改現有屬性

3、get、set

get、set會劫持你對物件資料的操作。

let data = {}
Object.defineProperty(data, 'key', {
  // value: 1,
  enumerable: true,
  configurable: false, // 不能再定義
  get: function () {
    // Dep.target &&  dep.addDep(Dep.target)
    return this.value
  },
  set: function (newVal) {
    if (newVal === this.value) {
      return
    }
    console.log(`發生了變化${this.value}=>${newVal}`)
    this.value = newVal
    // dep.notify() // 通知所有訂閱者
  }
})
複製程式碼

4、存在性

1、in:檢查物件及原型鏈

2、hasOwnProperty()

5、其他常見方法

  • Object.keys(),返回所有可列舉屬性
  • Object.values(),返回所有可列舉屬性的值
  • Object.entries(),返回所有可列舉屬性的鍵和值
  • Object.getOwnPropertyNames,返回所有屬性,不管是否可列舉

15、陣列

1、類陣列

具有length屬性,可以通過數字下標訪問元素,如arguments、獲取的DOM節點。Array.from(arguments)可以將一個類陣列轉化為陣列

2、陣列常見方法

  • push\pop:在陣列尾部新增刪除元素
  • unshift\shift:在陣列頭部新增刪除元素
  • concat:合併陣列
  • join:
  • slice:切片陣列,返回一個新的陣列
  • splice:刪除、修改、增加陣列元素
  • sort:排序,sort((a,b)=>{return a - b})

3、去重

// es6最簡單的方式
[...new Set(arr)]

function unique(arr) {
    let list = [...arr]
    let res = []
    list.forEach(item => {
        if(!res.include(item)) {
            res.push(item)
        }
    })
    return res
}
複製程式碼

相關文章