GeeksforGeeks articles

Javascript 进阶概念(ES5)

2018-12-15  本文已影响0人  gattonero

以下简称 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
typeof null //"object"
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

上一篇下一篇

猜你喜欢

热点阅读