JavaScript의 new 키워드는 인스턴스화와 상속을 구현할 수 있지만 개인적으로는 new 키워드를 사용하는 것이 최선의 방법은 아니며, 좀 더 친숙한 구현이 있을 수 있다고 생각합니다. 이 글에서는 new 키워드 사용 시의 문제점을 소개하고, 더 빠르고 이해하기 쉬운 구현을 제공하기 위해 new와 관련된 일련의 객체 지향 작업을 캡슐화하는 방법을 소개합니다.
두 개의 클래스, <a href="//m.sbmmt.com/wiki/164.html" target="_blank가 있다고 가정합니다. ">Class<code><a href="//m.sbmmt.com/wiki/164.html" target="_blank">Class</a>:function Class() {}
:function Class() {} 및 SubClass:function SubClass(){}
, SubClass는 Class에서 상속해야 합니다. 전통적인 메소드는 일반적으로 다음 단계에 따라 구성 및 구현됩니다. Class의
상속된속성과 메소드는 Class In에 배치되어야 합니다. SubClass
의 프로토타입 속성은 자신의 메소드와 속성 도 자신의 프로토타입 속성
new SubClass() Object.create(Class.prototype) | | V V SubClass.prototype ---> { } { }.proto ---> Class.prototype
// 构造函数/基类 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();
포함하는 것을 잊어버린 경우 이는 새 개체에 바인딩되지 않습니다. 안타깝게도 전역 개체에 바인딩됩니다. 따라서 새 개체를 확장하는 대신 전역 변수를 방해하게 됩니다. 컴파일 경고도 없고 런타임 경고도 없습니다. (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");
}
// 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는 JavaScript가 "인기"를 얻기 위해 Java와 같은 구문을 수용했던 시절의 잔재입니다. Visual Basic과 같은 보완 언어가 Microsoft의 언어 계열에서 C++에 있었던 것처럼 우리는 이를 Java의 동생으로 추진했습니다. Douglas는 이 문제를 다음과 같이 설명했습니다. 이 간접적인 방법은 고전적인 교육을 받은 프로그래머에게 언어를 더 친숙하게 보이도록 하기 위한 것이었지만 Java 프로그래머가 JavaScript의 생성자 패턴에 대해 가지고 있는 매우 낮은 의견에서 알 수 있듯이 그렇게 하지 못했습니다. 또한 JavaScript의 진정한 프로토타입 특성을 모호하게 만들었습니다. 결과적으로 언어를 효과적으로 사용하는 방법을 아는 프로그래머는 거의 없습니다.간단히 말하면 JavaScript는 프로토타입 언어입니다. 처음 만들어졌을 때 시장의 요구에 부응하고 사람들이 Java와 동일하다는 느낌을 갖도록 하기 위해 새로운 키워드가 도입되었습니다. Javascript는 프로토타입 기능을 통해 인스턴스화 및 상속을 구현하도록 되어 있지만 새 키워드로 인해 설명이 불가능해졌습니다.
既然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
至此,基本框架已经搭建起来,接下来继续补充功能。
我们希望把构造函数独立出来,并且统一命名为init。就好像Backbone.js
中每一个view都有一个initialize
方法一样。这样能让初始化更灵活和标准化,甚至可以把init构造函数借出去
我还想新增一个子类方法调用父类同名方法的机制,比如说在父类和子类的中都定义了一个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关键字,那么我们需要转投上两节中反复使用的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关键字,但是我们相当于实例化了一个对象(rectangle),给这个对象加上了自己的属性,并且成功调用了类(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关键字的基础上继续实现多继承?希望本文的内容在这里只是抛砖引玉,能够开拓大家的思路。
위 내용은 JavaScript에서 new 키워드를 사용하지 않는 이유에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!