《你不知道的JavaScript(上卷)》讀書筆記

第一次嘗試用思惟導圖記筆記,感受還不錯~~~不過仍是改不了我讀書筆記寫成抄書筆記的毛病 =。=html

由於開始學JS的時候,通常瀏覽器就已經支持ES6了,因此比較喜歡使用ES6語法,let,=>等,文中代碼不是抄書的,都用了ES6。vue

 

做用域和閉包

 

this和對象原型 

1. 屬性描述符(ES5開始)

獲取屬性描述符: node

var myObject = { a:2 }; Object.getOwnPropertyDescriptor( myObject, "a" ); // {
     // value: 2,
     // writable: true,
     // enumerable: true,
     // configurable: true  // }

設置屬性描述符,被設置的屬性能夠定義過,也能夠未定義過,es6

var myObject = {}; Object.defineProperty( myObject, "a", { value: 2, writable: false, // 不可寫! 
     configurable: true, enumerable: true });

 

其中:數組

writable 決定是否能夠修改屬性的值,若是設置爲 false。修改屬性值會靜默失敗(silently failed),嚴格模式會報錯,TypeError。瀏覽器

configurable 決定屬性是否能夠配置。很顯然,把configurable設置爲false是單項的。而且沒法撤銷。安全

                  即使屬性是 configurable:false,咱們仍是能夠 把 writable 的狀態由 true 改成 false,可是沒法由 false 改成 true。閉包

                  configurable:false 還會禁止刪除這個屬性,致使刪除靜默失敗。app

enumerable 控制屬性是否會出如今對象的屬性枚舉中,默認爲true。for..in 遍歷的是可枚舉屬性。dom

 

2. 訪問描述符

當給一個屬性定義 getter、setter 或者二者都有時,這個屬性會被定義爲「訪問描述 符」(和「數據描述符」相對)。對於訪問描述符來講,JavaScript 會忽略它們的 value 和 writable 特性,取而代之的是關心 set 和 get(還有 configurable 和 enumerable)特性。

var myObject = { // 給 a 定義一個setter
 get a() { return 2 } } Object.defineProperty( myObject, // 目標對象
    'b',            // 屬性名
    {               // 描述符
        // 給 b 設置一個 getter
        get: function() { return this.a * 2 }, // 確保 b 會出如今對象的屬性列表中
        enumerable: true } ) console.log(myObject.a) // 2
console.log(myObject.b) // 4
 a = 3 b = 5 console.log(myObject.a) // 2
console.log(myObject.b) // 4

getter 和 setter 通常是成對出現,若是隻出現一個,會致使 set 不生效 / get 到 undefined。

 

有個getter和setter,就能夠在設置數據的同時,作一些其餘的事情了,vue的雙向綁定,就是在數據set()裏更新dom元素,同時在dom的input事件更新數據,實現雙向綁定。代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <!-- HTML -->
    <div id="app">
        <input type="text" v-model="number">
        <button v-click="incre">+</button>
        <button v-click="minus">-</button>
        <button v-click="incre4">+4</button>
        <span v-bind="number"></span>
    </div>
    <!-- JavaScript -->
    <script>
        function MyVue(options) { // 先綁定基本數據
            this.$el = document.querySelector(options.el) // vue 綁定的 dom 元素
            this.$data = options.data this.$methods = options.methods // 根據 dom 獲取數據都綁定了哪些 dom 元素 並記錄 以便數據更新的時候 同步更新dom
            this._binding = {} // 初始化爲空數組
 Object.keys(this.$data).forEach((item) => { this._binding[item] = [] }) this._complie(this.$el) console.log(this._binding) Object.keys(this.$data).forEach((item) => { // 這裏對value的使用是一個閉包?...
 let value = this.$data[item] Object.defineProperty(this.$data, item, { get: () => { console.log(`獲取${item}: ${value}`) return value }, set: (val) => { // 更新 data 的時候要把相關 dom 節點所有更新
 console.log(`更新${item}: ${val}`) if (val !== value) { value = val this._binding[item].forEach((meth) => { meth() }) } } }) }) } /** * @param {HTMLElement} root: vue 綁定的 dom 元素節點 **/ MyVue.prototype._complie = function(root) { // 若是有子節點
 const nodes = root.children for (let i = 0; i < nodes.length; i++) { const node = nodes[i] if (node.children.length) { this._complie(node) } // 若是是bind 證實綁定了某個數據 那麼改數據更改時 更改該處 dom
                if (node.hasAttribute('v-bind')) { const dataName = node.getAttribute('v-bind') const attr = (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA') ? 'value' : 'innerHTML' node[attr] = this.$data[dataName] // 初始化頁面
                    this._binding[dataName].push(() => { console.log('v-bind: ', node, attr, dataName) node[attr] = this.$data[dataName] }) } // 若是有 v-click 就在點擊事件中執行methods中對應的那個函數
                if (node.hasAttribute('v-click')) { const methName = node.getAttribute('v-click') const method = this.$methods[methName] node.onclick = method.bind(this.$data) // method是對data中的數據進行操做,這裏記得要把this綁到data上
 } // 數據更改時更新 dom 節點 dom 節點更改時也更新 data
                if (node.hasAttribute('v-model')) { const dataName = node.getAttribute('v-model') node.value = this.$data[dataName] // 初始化頁面
                    this._binding[dataName].push(() => { node.value = this.$data[dataName] }) node.addEventListener('input', () => { console.log('v-model', node) this.$data[dataName] = node.value }) } } } window.onload = function() { const app = new MyVue({ el: '#app', data: { number: 0, c: 1 }, methods: { incre: function() { console.log('incre...', this) this.number++ }, minus: function() { console.log('minus...', this) this.number-- }, incre4: function() { console.log('incre4...', this) this.number = Number(this.number) + 4 } } }) } </script>
</body>
</html>
View Code

詳見:https://juejin.im/post/5acc17cb51882555745a03f8

 

 

3. call、apply、bind 的聯繫和區別

他們都是定義在Function.prototype裏面的函數,都有綁定this的功能。原型以下:

call(thisArg: any, args...: any)

apply(thisArg: any, argArray: Array)

apply能夠把存放參數數組直接傳遞,若是參數存在一個數組裏,使用apply將很方面。

能夠經過call來將某個對象的函數應用在其餘對象: Object.prototype.toString.call(something) 

bind也能夠綁定this,並返回一個函數,同時bind還有一個功能,就是綁定傳入的函數。

function sayHello(arg1, arg2) { console.log(arg1, arg2) console.log('hello, i am ' + this.name) } name = 'global' let p1 = { name: 'xiaoming' } let p2 = { name: 'hanmeimei' } sayHello('arg1', 'arg2')                    // i am global
sayHello.apply(p1, ['apply1', 'apply2'])    // i am xiaoming
sayHello.apply(p2, ['apply1', 'apply2'])    // i am hanmeimei
sayHello.call(p1, 'call1', 'call2')         // i am xiaoming
sayHello.call(p2, 'call1', 'call2')         // i am hanmeimei
 let sayHelloWithBind = sayHello.bind(p1, '參數1') sayHelloWithBind('參數2') // 參數1 參數2 hello, i am xiaoming

若是使用內置的 .bind(..) 函數來生成一個硬綁定函數的話, 該函數是沒有 .prototype 屬性的。在這樣的函數上使用 instanceof 的話, 目標函數的 .prototype 會代替硬綁定函數的 .prototype。

function Foo() {} Bar = Foo.bind({}) a = new Bar() console.log(a instanceof Foo) // true
console.log(a instanceof Bar) // true

 

4. import和export

有點多,不想寫了。。。。參考 Module 的語法

 

 

5. 類與對象 

JavaScript沒有構造函數,只有函數的構造調用,

JavaScript沒有類,只有對象

 

瞭解一下隨意的 constructor 

function Foo() { ... }
var a = new Foo(); a.constructor === Foo; // true

constructor,所謂的「構造函數」,實際上是Foo.prototype的,a自己並無這個屬性 a.hasOwnProperty('constructor') // false 

也就是說 Foo.prototype.constructor === Foo; // true 而和a是怎麼生成的沒有什麼關係。

若是先設置 Foo.prototype={ ... } 那麼 var a = new Foo(); 生成的a對象的constructor也就是Object。

a在new的時候,關聯到了Foo.prototype,若是你修改了 Foo.prototype = ...   a所關聯的對象是不變的。

 

.constructor 並非一個不可變屬性。它是不可枚舉的,可是它的值是可寫的。此外,你能夠給任意 [[Prototype]] 鏈中的任意對象添加一個名 爲 constructor 的屬性或者對其進行修改,你能夠任意對其賦值。

綜上,.constructor 是一個很是不可靠而且不安全的引用。一般來講要儘可能避免使用這些引用。

 

原型繼承

function Foo(name) { this.name = name } Foo.prototype.myName = function() { return this.name } function Bar(name, label) { Foo.call(this, name) this.label = label } // 爲 Bar.prototype 重新賦值一個 [[Prototype]] 爲 Foo.prototype 的對象 // 此時 Bar.prototype 是 沒有constructor 屬性的
Bar.prototype = Object.create(Foo.prototype) Bar.prototype.myLabel = function() { return this.label } var a = new Bar('a', 'obj a') console.log(a.myName()) console.log(a.myLabel())

咱們來對比一下兩種把 Bar.prototype 關聯到 Foo.prototype 的方法:

// ES6 以前須要拋棄默認的 Bar.prototype
Bar.ptototype = Object.create( Foo.prototype ) // ES6 開始能夠直接修改現有的 Bar.prototype
Object.setPrototypeOf( Bar.prototype, Foo.prototype )

 

JavaScript中沒有類,只有對象,因此沒有類繼承,只有對象的委託,經過 b=Object.create(a) 能夠將 b 的[[Prototype]] 屬性設爲 a,這樣當在b中查找屬性找不到的時候就能夠找到a。a中查找不到就繼續沿原型鏈查找。最終通常會查找到Object.prototype。默認字面量對象的[[Prototype]]是Object.prototype。這樣,只要在Object.prototype上定義一些函數toString(), valueOf()等,全部對象均可以使用。

new Foo() 操做能夠生成一個對象,而後將對象的[[Prototype]] 綁定到Foo.prototype,並將Foo的this綁定爲這個新函數,若是Foo()沒有返回值的話,該函數將做爲返回值。能夠看出new像是一個輔助功能,來方面在JS中模擬相似類的操做。注意,若是Foo返回了一個對象,那個new返回的也就是那個對象,新生成的對象將被拋棄。而那個對象,可能和Foo沒有任何關係。

 

對象關聯(OLOO, objects linked to other objects)

Task = { setID: function(ID) { this.id = ID; }, outputID: function() { console.log(this.id); } }; // 讓XYZ委託Task
XYZ = Object.create(Task); XYZ.prepareTask = function(ID, Label) { this.setID(ID); this.label = Label; }; XYZ.outputTaskDetails = function() { this.outputID(); console.log(this.label); }; // ABC = Object.create( Task );  // ABC ... = ...
 XYZ.prepareTask('123', 'Task-xyz') XYZ.outputTaskDetails()

在上面的代碼中,id 和 label 數據成員都是直接存儲在 XYZ 上;在委託行爲中咱們會盡可能避免在 [[Prototype]] 鏈的不一樣級別中使用相同的命名,不然就須要使用笨拙而且脆弱的語法來消除引用歧義。你沒法在兩個或兩個以上互相(雙向)委託的對象之間建立循環委託。

 

其實對象關聯的風格更容易理解,而原型模式反而像是爲了「模擬類」而出現的風格。直接經過new操做符來執行函數的內容,同時將對象的原型連接到函數的prototype。instanceof 專門用來檢查經過這個方法建立對象後兩這的關聯。

 

理解了這本書講的內容,其實這張圖也不是很難看懂……

 

 

Function.prototype = Function.__proto__ = Object.__proto__ Function.prototype.__proto__ = Function.__proto__.__proto__ = Object.__proto__.__proto__ = Object.prototype Object.prototype.__proto__ = null // Object.prototype 是對象
 Function.prototype.prototype = undefined // Function.prototype 是函數
相關文章
相關標籤/搜索