美文网首页GeeksforGeeks articles
Javascript 进阶概念(ES5)

Javascript 进阶概念(ES5)

作者: gattonero | 来源:发表于2018-12-15 15:51 被阅读0次

    以下简称 Javascript 为 JS,ECMAScript 为 ES
    函数特指 function,不是广义上的函数
    实例和对象指的都是通过函数new出的东西


    问题

    哪些值或对象是相等的,怎么判断

    解决

    equality table

    JS中判断相等有两种方式,== 和 ===
    灰色表示部分相等,也就是用 == 判断是相等的,而 === 判断是不等的,绿色表示全等,其余的不等

    实际上用 == 判断时,会有一个值的强转,而用 === 判断时,首先会判断两遍的类型,也就是typeof运算符,所以如果确定等式两遍一定是相同的类型,就用==,反之则用===,具体的全等判断规则见文档

    问题

    Javascript中的对象和函数创建和继承是如何实现的

    解决

    对象的创建

    如果在ES6中以OOP的概念去写的话,那这个问题的范围就不止是JS了。不过众所周知ES6中的class只是语法糖,具体实现还是原来那一套,那么先来看一下ES5官方文档中的概括

    一个对象是内置类型Object的实例,由一些属性来定义的,并且这些属性拥有独特的定义,比如是否可写等等。属性也是其他对象,方法(函数类型)或者基本类型(boolean number string Symbol undefined null)的容器。

    当然,还有许多内置对象,用于各种方面以丰富JS的语义

    上面是一些总结性的陈述,具体是怎么构建对象的,原型链又是什么,我们继续看官方文档

    首先,我们研究通过原型创建的对象

    对于这种对象,我们需要通过constructor来构造,然后再声明对象的属性。constructor是一个拥有"prototype"方法的函数,我们通过给函数传入参数来创造新的对象,例如new Date(2009,11)创造了一个新的日期对象,如果不使用new关键字,那么相当于仅仅执行函数

    通过原型创建的对象中,有一个对其constructor的函数的prototype属性的隐含的引用,我们叫做对象原型

    constructor的prototype中,也会有这样的引用,指向下一个constructor的prototype,我们叫这个原型链,引用属性时,也会基于这个原型链从浅到深依次查找(原文英文非常绕,想起了GRE题目)

    通过Object.create()创建
    根据文档,该方法可以直接创建基于指定原型的对象

    var a = {}, b = Object.create(a)
    b.__proto__ === a //true
    

    通过字面量(Object Literal)创建

    var a={id:0}
    a.__proto__ === Object.prototype
    

    a.proto===Object.prototype

    通过函数创建
    var F = function(){}
    var F0 = new F

    函数的创建

    常规函数
    我们在chrome环境中做一个实验,创建一个空函数F,然后用这个函数构造一个对象F0,于是有

    const F = function(){}
    var F0 = new F
    
    F0.constructor === F//true
    F0.__proto__ === F.prototype//true
    F.prototype.constructor === F//true
    
    F.__proto__ === Function.prototype//true
    
    F.prototype.__proto__ === Object.prototype//true
    

    这里简单地解释一下F和F0的原型链,函数对其原型(F.prototype)进行包装构造对象F0,F0.__proto__指针指向函数原型(F.prototype

    原型链上,Function >---> F >---> F0,Object >---> Function.prototype

    那么,既然JS中的“类”仅仅是Function类型的实例对指定原型的包装,那么我们可以任意构造一个对象,将它作为原型,来构造新的对象吗?

    显然可以:

    var someObject = {
        arr:[],
        func(a){this.b=a},
        b:0
    }
    var someFunction = function (){}
    someFunction.prototype = someObject
    var c1 = new someFunction, c2 = new someFunction
    c1.__proto__===someObject //true
    c1.__proto__ === c2.__proto__ //true
    
    c1.b === c2.b//true,引用的都是原型属性
    c1.func(1)
    c1.b === c2.b//false
    c1.hasOwnProperty('b')//true 修改成自身属性
    
    //原型对象的属性最好都是基本类型
    c1.arr.push(1)//1
    c2.arr.length//1
    

    问题来了,怎么实现c1和c2不共享arr呢,那么就要引入constructor

    var someFunction = function (){
      this.arr = []
    }
    someFunction.prototype = someObject
    var c1 = new someFunction, c2 = new someFunction
    c1.hasOwnProperty('arr')//true
    c1.arr.push(1)
    c2.arr.length//0
    

    那么问题又来了,函数的constructor能不能是另一个函数

    var someFunction = function (){}
    someFunction.prototype = someObject
    someFunction.prototype.constructor = function(){
      this.arr=[]
    }
    var c1 = new someFunction, c2 = new someFunction
    c1.hasOwnProperty('arr')//false
    

    原因其实在于someFunction.prototype的确是函数绑定的原型没错,但是修改原型的constructor引用(原本指向函数本身),并没有修改someFunction函数本身,执行new someFunction时,只会使用函数本身来构造,所以即使构造出的对象有c1.__proto__.constructor === someConstructor,someConstructor也起不到任何构造函数的作用

    立即执行函数
    函数的定义和执行放在一起,可以理解为一个代码段,有两种方式,常见于各种js库中

    ;(function(){
      ...
    }())
    
    ;(function(){
      ...
    })()
    

    通过Function定义

    var F= new Function('a','b','return a+b')
    F(1,2)//3
    
    函数的继承

    根据上面的分析可知,JS中的继承只是基于原型链的复制,继承的并不是函数而是对象,而且原型链顾名思义不支持多重继承(一个对象的原型指针只能指向另一个对象),我们可以从CryptoJS库中分析继承的实现

    var Base = C_lib.Base = (function () {
    
    
            return {
                /**
                 * Creates a new object that inherits from this object.
                 *
                 * @param {Object} overrides Properties to copy into the new object.
                 *
                 * @return {Object} The new object.
                 *
                 * @static
                 *
                 * @example
                 *
                 *     var MyType = CryptoJS.lib.Base.extend({
                 *         field: 'value',
                 *
                 *         method: function () {
                 *         }
                 *     });
                 */
                extend: function (overrides) {
                    // Spawn
                    var subtype = create(this);
    
                    // Augment
                    if (overrides) {
                        subtype.mixIn(overrides);
                    }
    
                    // Create default initializer
                    if (!subtype.hasOwnProperty('init') || this.init === subtype.init) {
                        subtype.init = function () {
                            subtype.$super.init.apply(this, arguments);
                        };
                    }
    
                    // Initializer's prototype is the subtype object
                    subtype.init.prototype = subtype;
    
                    // Reference supertype
                    subtype.$super = this;
    
                    return subtype;
                },
    
                /**
                 * Extends this object and runs the init method.
                 * Arguments to create() will be passed to init().
                 *
                 * @return {Object} The new object.
                 *
                 * @static
                 *
                 * @example
                 *
                 *     var instance = MyType.create();
                 */
                create: function () {
                    var instance = this.extend();
                    instance.init.apply(instance, arguments);
    
                    return instance;
                },
    
                /**
                 * Initializes a newly created object.
                 * Override this method to add some logic when your objects are created.
                 *
                 * @example
                 *
                 *     var MyType = CryptoJS.lib.Base.extend({
                 *         init: function () {
                 *             // ...
                 *         }
                 *     });
                 */
                init: function () {
                },
    
                /**
                 * Copies properties into this object.
                 *
                 * @param {Object} properties The properties to mix in.
                 *
                 * @example
                 *
                 *     MyType.mixIn({
                 *         field: 'value'
                 *     });
                 */
                mixIn: function (properties) {
                    for (var propertyName in properties) {
                        if (properties.hasOwnProperty(propertyName)) {
                            this[propertyName] = properties[propertyName];
                        }
                    }
    
                    // IE won't copy toString using the loop above
                    if (properties.hasOwnProperty('toString')) {
                        this.toString = properties.toString;
                    }
                },
    
                /**
                 * Creates a copy of this object.
                 *
                 * @return {Object} The clone.
                 *
                 * @example
                 *
                 *     var clone = instance.clone();
                 */
                clone: function () {
                    return this.init.prototype.extend(this);
                }
            };
    }());
    

    其中,C_lib可以看做空对象{},create就是Object.create(上面的polyfill过于繁琐,MDN上有简单的版本),我们可以看到这里使用一个立即执行函数包裹了Base对象,并给出了extend方法,可以返回一个继承于Base的对象var extended = Base.extend({}),其关键在于extended本身是一个对象,但是extended.init是一个构造器,可以以这个对象为原型构造实例,生成的实例有指针可以指向原型对象和父对象。可以说完美实现了ES5的思想

    var extended = Base.extend({mixinProp:1})
    (new extended.init).mixinProp//1
    (new extended.init).__proto__ === extended//true
    (new extended.init).__proto__.$super === Base//true
    

    Tips

    • 不要用等于号判断你不知道类型的对象的理由:
    [1]==true//true
    [0]==true//false
    
    • NaN不等于任何东西,包括自己
    • Object.create(null) 可以创建一个全空对象,不在任何原型链上
    • 因为null的二进制表达全部是0,而typeof会根据该值的前三位是不是0来判断是否是对象,所以null的类型是对象,这其实是一个bug,null是属于六种内置类型之一的,结果应该是"null"
    typeof null //"object"
    
    • 只要定义的不是匿名函数,那么函数就会自动获得name属性
    var F = function() {};
    F.name // "F"
    
    var F = function FF() {};
    F.name // "FF"
    
    // ES5
    function D() {
      Array.apply(this, arguments);
    }
    D.prototype = Object.create(Array.prototype);
     
    var d = new D();
    d[0] = 42;
     
    d.length; // 0
    

    官方解释:

    Partial support
    Built-in subclassability should be evaluated on a case-by-case basis as classes such as HTMLElement can be subclassed while many such as Date, Array and Error cannot be due to ES5 engine limitations.

    https://codeburst.io/various-ways-to-create-javascript-object-9563c6887a47
    https://dmitripavlutin.com/6-ways-to-declare-javascript-functions/
    http://www.ecma-international.org/ecma-262/6.0/index.html
    https://www.geeksforgeeks.org/advanced-javascript-backend-basics/
    https://www.css88.com/archives/7383
    https://babeljs.io/docs/en/learn/#ecmascript-2015-features-subclassable-built-ins

    相关文章

      网友评论

        本文标题:Javascript 进阶概念(ES5)

        本文链接:https://www.haomeiwen.com/subject/opkbhqtx.html