zoukankan      html  css  js  c++  java
  • 读书笔记-你不知道的JS上-混入与原型

    继承

     

    mixin混合继承

        function mixin(obj1, obj2) {
            for (var key in obj2) {
                //重复不复制
                if (!(key in obj1)) {
                    obj1[key] = obj2[key];
                }
            }
            return obj1;
        }

      这种复制是浅复制,对象或者数组函数等都是同一个引用,改变obj1的会同时影响obj2。

     

    寄生继承

      ...

     

    隐式继承

      子类调用fn.call(this)

     

      深拷贝需要重新声明一个变量(对象),遍历(递归)复制,详情见我的函数技巧,不贴出来了。

     

     

    原型

      Javascript对象中有一个特殊的[[prototype]]内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时[[prototype]]属性都会被赋予一个非空的值。

        var f = {
            a: 1
        };
        // 创建一个对象 原型为f
        var f2 = Object.create(f);
        // 通过原型链找到了属性a
        console.log(f2.a);

      使用for..in遍历对象的原理和原型链类似,任意可枚举在原型链上的属性都会被遍历。使用in操作符检查属性时也会查找对象原型链,无论是否可枚举。

      所有普通对象的原型最终指向Object.prototype。

      详细讲解一个对象赋值语句:

        var obj = {};
        obj.a = 1;

      这里有四种情况:

      1、obj中存在a属性,就会被修改。

        var obj = {
            a: 2
        };
        obj.a = 1;
        console.log(obj.a); //1

      2、obj的原型链不存在a属性,就会被直接添加到obj上。

        var obj = {};
        console.log('a' in obj); //false
        obj.a = 1;
        console.log(obj.a); //1

      3、obj与obj的原型链都存在a属性,就会发生屏蔽,obj中的a会屏蔽原型链上的a。

        var obj2 = {
            a: 2
        };
        var obj = Object.create(obj2);
        obj.a = 1;
        console.log(obj.a); //1

      4、obj的原型链上存在a属性,而obj不存在时,会出现多种情况。

      在原型链上存在a属性且没有被标记为只读,那就会直接在obj添加一个a属性。(情况3)

      在原型链上存在a属性且被标记为只读,那么无法创建该属性,严格模式会报错,普通模式赋值语句会被忽略。

        // 在'use strict'模式下
        // Cannot assign to read only property 'a' of object '#<Object>'
        var obj2 = {};
        Object.defineProperty(obj2, 'a', {
            value: 2,
            configurable: true,
            enumerable: true,
            writable: false
        })
        var obj = Object.create(obj2);
        obj.a = 1; //无效
        console.log(obj.a); //2

      如果在原型链上存在a并且它是一个setter,那就一定会调用这个setter。a不会被添加到obj,也不会重新定义setter。

        var obj2 = {
            set a(val) {
                console.log(1);
            }
        };
        var obj = Object.create(obj2);
        obj.a = 1; // 执行set并输出1

      如果希望怎么样都添加属性,请使用Object.defineProperty(...)。

     

    关于prototype

      所有函数默认都会拥有一个名为prototype的公有不可枚举属性,它会指向另外一个对象:

        function fn() {
            console.log(1);
        }
        console.log(fn.prototype); //Object{}

     

      这个对象通常被称为fn的原型,实际上不如叫fn.prototype。

        function fn() {
            console.log(1);
        }
        var f = new fn();
        console.log(f.__proto__ === fn.prototype); //true

      在调用new fn()时,会创建一个对象,并给一个内部[[prototype]]链接,连接到fn.prototype。个人感觉__proto__这个浏览器私有实现的属性叫原型比较好,毕竟原型链是通过这个属性向上查找的。

      实际上,new操作符实际上并没有直接创建关联,这只是一个副作用。

      通过Object.create()方法才是正规创建原型链接的方法。

      上一段代码很容易让人认为fn是一个构造函数,因为这里用new来调用它并构造出一个对象。

      实际上,fn和普通的函数没有区别。函数本身不是构造函数,当在普通的函数前面加上new时,就会把这个函数调用变成了一个‘构造函数调用’。实际上,new会劫持所有普通函数并用构造形式来调用它。

      

      考虑下面一段代码。

        function fn(a) {
            this.a = a;
        }
        fn.prototype.getA = function() {
            return this.a;
        }
        var f1 = new fn(1);
        var f2 = new fn(2);
        console.log(f1.getA()); //1
        console.log(f2.getA()); //2

       这段代码展示了两种面向类的技巧:

      1、this.name=name给每个对象都绑定了.name属性。

      2、fn.prototype.getA=...给原型添加了一个方法,现在,每一个实例都可以调用getA方法。

      看起来,似乎创建f1、f2时会把对象复制到这两个新对象中,然而实际上只是通过原型链向上查找调用了方法而已。

     

    关于constructor

        function fn1() {};
        var f1 = new fn1();
        console.log(f1.constructor === fn1); //true
        //替换默认原型
        function fn2() {};
        fn2.prototype = {};
        var f = new fn2();
        console.log(f.constructor === fn2); //false
        console.log(f.constructor === Object); //true

      当前用新对象替换fn原型时,new出来的新对象不会自动获得constructor属性。所以,不能说因为f.constructor===fn属性,就认为fn构造了对象f。

      实际上,new出来的对象f并没有.constructor属性,它会委托原型去查找该属性,默认的原型(fn.prototype)有construtor属性并且指向fn,所以f.constructor(实际上调用的是fn.prototype.constructor)会指向fn。但是如果替换了fn.prototype,新的原型对象并不会默认有.construtor,于是委托会一直提交到Object.prototype,恰好Object.prototype.constructor===Object,结果也在上面代码中展示出来了。

      可以手动给新原型添加constructor属性:

        function fn2() {};
        fn2.prototype = {};
        fn2.prototype.constructor = fn2; //修正原型链
        var f = new fn2();
        console.log(f.constructor === fn2); //true
        console.log(f.constructor === Object); //false

      看,修复了!(实际上应该用Object.defineProperty来定义constructor,因为该属性应该是不可枚举的)

      所以说,constructor并不是一个不可变属性,它只是默认不可枚举,但是值可以被任意修改。

     

    原型继承

      常见误用形式和正确使用方式:

        function fn1() {};
    
        function fn2() {};
        //不可以 只是复制引用
        //fn1.prototype = fn2.prototype;
        //可以实现 但是会执行fn2函数 可能出现额外问题
        //fn1.prototype=new fn2;
        //ES6前 需要抛弃fn1默认的prototype 可能还要修正constructor属性
        fn1.prototype = Object.create(fn2.prototype);
        //ES6语法 直接修正默认prototype
        Object.setPrototypeOf(fn1.prototype, fn2.prototype);

      

      如何找出任意对象的原型链呢?有一个方法是instanceof。

        function fn() {}
        var f = new fn;
        console.log(f instanceof fn); //true

      instanceof操作符左边是一个对象,右边是一个函数。该操作解决的问题是:在f的原型链上,是否有fn.prototype对象?(通过bind强绑生成的函数没有prototype属性)

      如果要直接判断两个对象是否存在原型关系,可以用以下几个方法:

        function fn() {}
        var f = new fn;
        //是否是原型关系
        console.log(fn.prototype.isPrototypeOf(f)); //true
        //展示原型
        console.log(Object.getPrototypeOf(f)); //Object{}
        //浏览器私有实现
        console.log(f.__proto__); //Object{}

      绝大多数浏览器支持__proto__方法来访问[[prototype]]属性。(__开头的属性表明这不是ECMA标准,还有很多其他的属性也以__开头)

      现在ES6可以用Object.getPrototypeOf()与Object.setPropertyOf()来获取和设置原型,相当于原生支持了__proto__。

      

      Object.create()会创建一个对象,并关联到参数对象中,避免了new操作符与生成对应的constructor,prototype。

      如果旧浏览器不支持,可以用下面的代码模拟:

        if (!Object.create) {
            Object.create = function(o) {
                function f() {};
                f.prototype = o;
                return new f();
            }
        }

      关于new操作符和原型,如果下面的代码可以理解,那就没问题了~

        function fn(a) {
            this.a = a;
        }
        fn.prototype = {};
        Object.defineProperty(fn.prototype, 'a', {
            value: 1,
            configurable: true,
            enumerable: true,
            writable: false
        });
        //严格模式new会报错
        var f = new fn(3);
        console.log(f); //无效!
        console.log(f.a); //1

      完结撒花!

     

  • 相关阅读:
    【作业四】软件案例分析之必应词典
    【番外篇001】意外的“黄马褂”
    收藏的好资源
    【作业三】结队任务二-----CourseManagement
    【作业二】结对项目之需求分析与原型模型设计
    【实践练习一】Git以及Github的使用
    [iOS 多线程 & 网络
    Objective-c 单例设计模式
    优化tableView性能(针对滑动时出现卡的现象)
    iOS应用程序生命周期(前后台切换,应用的各种状态)详解
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/6477143.html
Copyright © 2011-2022 走看看