zoukankan      html  css  js  c++  java
  • 《你不知道的JavaScript(上卷)》读书笔记

    第一次尝试用思维导图记笔记,感觉还不错~~~不过还是改不了我读书笔记写成抄书笔记的毛病 =。=

    因为开始学JS的时候,一般浏览器就已经支持ES6了,所以比较喜欢使用ES6语法,let,=>等,文中代码不是抄书的,都用了ES6。

    作用域和闭包

    this和对象原型 

    1. 属性描述符(ES5开始)

    获取属性描述符: 

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

    设置属性描述符,被设置的属性可以定义过,也可以未定义过,

    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 还会禁止删除这个属性,导致删除静默失败。

    enumerable 控制属性是否会出现在对象的属性枚举中,默认为true。for..in 遍历的是可枚举属性。

    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 是函数
  • 相关阅读:
    好久没有上来,发布几个日志类
    这些天很忙,写一个类似防火墙的东西在WINSOCK2 SPI上做DLL,终于把问题解决了,现提供完整C++项目下载
    Flask学习之搭建环境
    年底总结前序
    3月13日的合照一张
    我也是个张三
    读《广州的一场春梦》有感
    四期合照,纪念小杨离开
    深夜的伤感
    随感,未写先累
  • 原文地址:https://www.cnblogs.com/wenruo/p/9329352.html
Copyright © 2011-2022 走看看