但个体以为利用new关键字并非是一级的实行

JavaScript中的new关键字能够兑现实例化和继续的专门的学业,但个体感觉利用new关键字实际不是是最棒的实行,还足以有更和睦一些的兑现。本文将介绍使用new关键字有怎么样难题,然后介绍怎样对与new相关联的后生可畏类别面向对象操作实行李包裹装,以便提供更迅捷的、更易令人清楚的达成格局。

金钱观的实例化与持续

大器晚成旦大家有五个类,Class:function Class() {}SubClass:function SubClass(){},SubClass须求后续自Class。守旧方法经常是按如下步骤来公司和得以完毕的:

  • Class中被持续的属性和艺术非得放在Class的prototype属性中

  • SubClass中和煦的点子和属性也必得放在本身prototype属性中
  • SubClass的prototype对象的prototype(__proto__卡塔尔(قطر‎属性必需指向的Class的prototype

那样一来,由于prototype链的本性,SubClass的实例便能追溯到Class的措施,进而实现三回九转:

new SubClass()      Object.create(Class.prototype)
    |                    |
    V                    V
SubClass.prototype ---> { }
                        { }.__proto__ ---> Class.prototype

举一个活灵活现的例证:上边包车型地铁代码中,大家做了以下几件事:

  • 概念二个父类叫做Human
  • 概念五个名叫Man的子类世袭自Human
  • 子类世襲父类的任何属性,并调用父类的布局函数,实例化这一个子类

// 构造函数/基类
function Human(name) {
    this.name = name;
}

/* 
    基类的方法保存在构造函数的prototype属性中
    便于子类的继承
*/
Human.prototype.say = function () {
    console.log("say");
}

/*
    道格拉斯的object方法(等同于object.create方法)
*/
function object(o) {
    var F = function () {};
    F.prototype = o;
    return new F();
}

// 子类构造函数
function Man(name, age) {
    // 调用父类的构造函数
    Human.call(this, name);
    // 自己的属性age
    this.age = age;
}

// 继承父类的方法
Man.prototype = object(Human.prototype);
Man.prototype.constructor = Man;

// 实例化子类
var man = new Man("Lee", 22);
console.log(man);
// 调用父类的say方法:
man.say();

DEMO

因此地点的代码能够总括出守旧的实例化与持续的几本性子:

  • 理念方法中的“类”一定是八个布局函数。
  • 质量和措施绑定在prototype属性上,并依靠prototype的特色实现持续。
  • 透过new关键字来实例化叁个指标。

怎么笔者会十分的分明Object.create方法与DougRuss的object方法是同等呢?因为在MDN上,object方法正是用作Object.create的多个Polyfill方案:

  • Object.create
  • Douglas Crockford’s object
    method

new关键字的白璧微瑕

在《Javascript语言精髓》(Javascript: The Good
Parts)中,道格拉斯感到应当幸免选用new关键字:

If you forget to include the new prefix when calling a constructor
function, then this will not be bound to the new object. Sadly, this
will be bound to the global object, so instead of augmenting your new
object, you will be clobbering global variables. That is really bad.
There is no compile warning, and there is no runtime warning. (page
49)

忽略是说在相应利用new的时候若是忘了new关键字,会引发部分标题。

自然了,你忘记使用任何重大字都会孳生一文山会海的难题。再退一步说,那么些主题材料是全然可避防止的:

function foo()
{   
   // 如果忘了使用关键字,这一步骤会悄悄帮你修复这个问题
   if ( !(this instanceof foo) )
      return new foo();

   // 构造函数的逻辑继续……
}

抑或更通用的抛出非常就可以

function foo()
{
    if ( !(this instanceof arguments.callee) ) 
       throw new Error("Constructor called as a function");
}

又或许依照John
Resig的方案,希图二个makeClass工厂函数,把抢先四分之意气风发的最初化效用放在贰个init方法中,而非结构函数自个儿中:

// makeClass - By John Resig (MIT Licensed)
function makeClass(){
  return function(args){
    if ( this instanceof arguments.callee ) {
      if ( typeof this.init == "function" )
        this.init.apply( this, args.callee ? args : arguments );
    } else
      return new arguments.callee( arguments );
  };
}

以作者之见,new关键字不是三个好的实施的机要原因是:

…new is a remnant of the days where JavaScript accepted a Java like
syntax for gaining “popularity”. And we were pushing it as a little
brother to Java, as a complementary language like Visual Basic was to
C++ in Microsoft’s language families at the time.

DougRuss将以此主题素材陈说为:

This indirection was intended to make the language seem more familiar
to classically trained programmers, but failed to do that, as we can
see from the very low opinion Java programmers have of JavaScript.
JavaScript’s constructor pattern did not appeal to the classical
crowd. It also obscured JavaScript’s true prototypal nature. As a
result, there are very few programmers who know how to use the
language effectively.

大约来说,JavaScript是风姿浪漫种prototypical类型语言,在开创之初,是为了龙攀凤附市镇的需求,让大家以为它和Java是看似的,才引进了new关键字。Javascript本应透过它的Prototypical天性来完成实例化和连续,但new关键字让它变得岂有此理。

把古板方法加以改动

既然如此new关键字非常不足本身,那么大家有五个格局能够化解那些主题材料:一是截然废弃new关键字,二是把带有new关键字的操作封装起来,只向外提供温馨的接口。上边将介绍第二种情势的落到实处思路,把古板办法加以改换。

我们初叶布局一个最原始的基类Class(相仿于JavaScript中的Object类),况兼只向外提供八个接口:

  • Class.extend 用于开展子类
  • Class.create 用于创建实例

// 基类
function Class() {}

// 将extend和create置于prototype对象中,以便子类继承
Class.prototype.extend = function () {};
Class.prototype.create = function () {};

// 为了能在基类上直接以.extend的方式进行调用
Class.extend = function (props) {
    return this.prototype.extend.call(this, props);
}

extend和create的实际完毕:

Class.prototype.create = function (props) {
    /*
        create实际上是对new的封装;
        create返回的实例实际上就是new构造出的实例;
        this即指向调用当前create的构造函数;
    */
    var instance = new this();
    /*
        绑定该实例的属性
    */
    for (var name in props) {
        instance[name] = props[name];
    }
    return instance;
}

Class.prototype.extend = function (props) {
    /*
        派生出来的新的子类
    */
    var SubClass = function () {};
    /*
        继承父类的属性和方法,
        当然前提是父类的属性都放在prototype中
        而非上面create方法的“实例属性”中
    */
    SubClass.prototype = Object.create(this.prototype);
    // 并且添加自己的方法和属性
    for (var name in props) {
        SubClass.prototype[name] = props[name];
    }
    SubClass.prototype.constructor = SubClass;

    /*
        介于需要以.extend的方式和.create的方式调用:
    */
    SubClass.extend = SubClass.prototype.extend;
    SubClass.create = SubClass.prototype.create;

    return SubClass;
}

如故以Human和Man类比如使用验证:

var Human = Class.extend({
    say: function () {
        console.log("Hello");
    }
});

var human = Human.create();
console.log(human)
human.say();

var Man = Human.extend({
    walk: function () {
        console.log("walk");
    }
});

var man = Man.create({
    name: "Lee",
    age: 22
});

console.log(man);
// 调用父类方法
man.say();

man.walk();

DEMO

从那之后,基本框架已经搭建起来,接下去继续补充功效。

  1. 咱俩期望把布局函数独立出来,而且统一命名称为init。就象是Backbone.js中每三个view都有叁个initialize措施风姿浪漫致。那样能让初步化更加灵活和法规,以至足以把init构造函数借出去
  2. 自家还想增加产能二个子类方法调用父类同名方法的机制,举个例子说在父类和子类的中都定义了三个say方法,那么大器晚成旦在子类的say中调用this.callSuper()就会调用父类的say方法了。举例:

// 基类
var Human = Class.extend({
    /*
        你需要在定义类时定义构造方法init
    */
    init: function () {
        this.nature = "Human";
    },
    say: function () {
        console.log("I am a human");
    }
})

var Man = Human.extend({
    init: function () {
        this.sex = "man";
    },
    say: function () {
        // 调用同名的父类方法
        this.callSuper();
        console.log("I am a man");
    }
});

那么Class.create就不光是new一个构造函数了:

Class.create = Class.prototype.create = function () {
    /*
        注意在这里我们只是实例化一个构造函数
        而非最后返回的“实例”,
        可以理解这个实例目前只是一个“壳”
        需要init函数对这个“壳”填充属性和方法
    */
    var instance = new this();

    /*
        如果对init有定义的话
    */
    if (instance.init) {
        instance.init.apply(instance, arguments);
    }
    return instance;
}

福寿绵绵在子类方法调用父类同名方法的建制,大家可以借用John
Resig的方案:

Class.extend = Class.prototype.extend = function (props) {
    var SubClass = function () {};
    var _super = this.prototype;
     SubClass.prototype = Object.create(this.prototype);
     for (var name in props) {
        // 如果父类同名属性也是一个函数
        if (typeof props[name] == "function" 
            && typeof _super[name] == "function") {
            // 重新定义用户的同名函数,把用户的函数包装起来
            SubClass.prototype[name] 
                = (function (super_fn, fn) {
                return function () {

                    // 如果用户有自定义callSuper的话,暂存起来
                    var tmp = this.callSuper;
                    // callSuper即指向同名父类函数
                    this.callSuper = super_fn;
                    /*
                        callSuper即存在子类同名函数的上下文中
                        以this.callSuper()形式调用
                    */
                    var ret = fn.apply(this, arguments);
                    this.callSuper = tmp;

                    /*
                        如果用户没有自定义的callsuper方法,则delete
                    */
                    if (!this.callSuper) {
                        delete this.callSuper;
                    }

                    return ret;
                }
            })(_super[name], props[name])  
        } else {
            // 如果是非同名属性或者方法
            SubClass.prototype[name] = props[name];    
        }

        ..
    }

    SubClass.prototype.constructor = SubClass; 
}

最后交给三个总体版,而且做了风华正茂部分优化:

function Class() {}

Class.extend = function extend(props) {

    var prototype = new this();
    var _super = this.prototype;

    for (var name in props) {

        if (typeof props[name] == "function" 
            && typeof _super[name] == "function") {

            prototype[name] = (function (super_fn, fn) {
                return function () {
                    var tmp = this.callSuper;

                    this.callSuper = super_fn;

                    var ret = fn.apply(this, arguments);

                    this.callSuper = tmp;

                    if (!this.callSuper) {
                        delete this.callSuper;
                    }
                    return ret;
                }
            })(_super[name], props[name])
        } else {
            prototype[name] = props[name];    
        }
    }

    function Class() {}

    Class.prototype = prototype;
    Class.prototype.constructor = Class;

    Class.extend =  extend;
    Class.create = Class.prototype.create = function () {

        var instance = new this();

        if (instance.init) {
            instance.init.apply(instance, arguments);
        }

        return instance;
    }

    return Class;
}

上边是测验的代码。为了印证方面代码的强壮性,故意完结了三层世襲:

var Human = Class.extend({
    init: function () {
        this.nature = "Human";
    },
    say: function () {
        console.log("I am a human");
    }
})

var human = Human.create();
console.log(human);
human.say();

var Man = Human.extend({
    init: function () {
        this.callSuper();
        this.sex = "man";
    },
    say: function () {
        this.callSuper();
        console.log("I am a man");
    }
});

var man = Man.create();
console.log(man);
man.say();

var Person = Man.extend({
    init: function () {
        this.callSuper();
        this.name = "lee";
    },
    say: function () {
        this.callSuper();
        console.log("I am Lee");
    }
})

var person = Person.create();
console.log(person);
person.say();

DEMO

是时候根本打消new关键字了

譬喻不采纳new关键字,那么大家须求转投上两节中反复使用的Object.create来临蓐新的对象

假定大家有三个矩形对象:

var Rectangle = {
    area: function () {
        console.log(this.width * this.height);
    }
};

依附Object.create,大家得以生成二个存有它抱有办法的靶子:

var rectangle = Object.create(Rectangle);

改换之后,咱们还足以给那几个实例赋值长度宽度,並且获得面积值

var rect = Object.create(Rectangle);
rect.width = 5;
rect.height = 9;
rect.area();

专一这几个进度大家尚无使用new关键字,可是我们一定于实例化了叁个对象(rectangleState of Qatar,给这么些指标加上了协和的品质,而且成功调用了类(Rectangle卡塔尔国的不二秘籍。

不过大家希望能自动化赋值长宽,没难点,这就定义贰个create方法:

var Rectangle = {
    create: function (width, height) {
      var self = Object.create(this);
      self.width = width;
      self.height = height;
      return self;
    },
    area: function () {
        console.log(this.width * this.height);
    }
};

运用办法如下:

var rect = Rectangle.create(5, 9);
rect.area();

在纯粹使用Object.create的体制下,我们曾经完全放任了布局函数那么些定义。一切都以对象,二个类也足以是指标,那几个类的实例可是是三个它和睦的仿制品。

上面看看怎样兑现三番两次。我们后天急需二个星型,世袭自这么些圆柱形

var Square = Object.create(Rectangle);

Square.create = function (side) {
  return Rectangle.create.call(this, side, side);
}

实例化它:

var sq = Square.create(5);
sq.area();

这种做法其实和我们第一种最基本的好像

function Man(name, age) {
    Human.call(this, name);
    this.age = age;
}

上边的格局仍旧太复杂了,大家期望进一步自动化,于是大家得以写那样贰个extend函数

function extend(extension) {
    var hasOwnProperty = Object.hasOwnProperty;
    var object = Object.create(this);

    for (var property in extension) {
      if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") {
        object[property] = extension[property];
      }
    }

    return object;
}

/*
    其实上面这个方法可以直接绑定在原生的Object对象上:Object.prototype.extend
    但个人不推荐这种做法
*/

var Rectangle = {
    extend: extend,
    create: function (width, height) {
      var self = Object.create(this);
      self.width = width;
      self.height = height;
      return self;
    },
    area: function () {
        console.log(this.width * this.height);
    }
};

像这种类型当大家需求三番两次时,就能够像前多少个主意生机勃勃致用了

var Square = Rectangle.extend({
    // 重写实例化方法
    create: function (side) {
         return Rectangle.create.call(this, side, side);
    }
})

var s = Square.create(5);
s.area();

结束语

本文对去new关键字的不二等秘书籍做了有的陈列,但专门的学业还远远未有甘休,有这一个多的地点值得扩充,譬如:怎么珍视新定义instance of艺术,用于推断三个目标是不是是二个类的实例?怎么着在去new关键字的幼功上连战皆捷落到实处多三番一次?希望本文的内容在这里边只是一得之见,能够开采大家的笔触。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website