学习prototypejs中的继承实现机制(一): Object.extend()、Class.create()、Class#addMethods()的使用[通俗易懂]

学习prototypejs中的继承实现机制(一): Object.extend()、Class.create()、Class#addMethods()的使用[通俗易懂]$super是不是和java中的继承有点像,我们居然可以使用$super来调用父类中的方法。不过有些差别:java中可以使用super调用父类中的任何公开的方法,但是在prototypejs里面$super只是一个方法,不是父对象。我们先研究下,prototypejs是如何做到$super,后面再看我们能不能改造它,让$super更像java中的super关键字。

大家好,又见面了,我是你们的朋友全栈君。

同underscore.js库一样,prototypejs也提供了丰富的工具方法来操作javascript内置对象(Array,Function,Object)等。工具方法不做过多介绍,自己去看就好,这里主要关注下prototypejs继承的实现机制。我使用的是prototype-1.7.3.js,我们看一段有趣的代码:

var Animal = Class.create({
      initialize: function(name, sound) {
        this.name  = name;
        this.sound = sound;
      },
  
      speak: function() {
        console.log("in Animal="+this.name + " says: " + this.sound + "!");
      }
    });
	
var Dog = Class.create(Animal, {
	initialize: function($super, name) {
		$super(name, 'dog-sound');
	},
	speak: function($super){
		$super();
		console.log("Haha in Dog.");
	}
});

var oneDog = new Dog("aty's dog");
oneDog.speak();
// output:
// in Animal=aty's dog says: dog-sound!
// Haha in Dog.

是不是和java中的继承有点像,我们居然可以使用$super来调用父类中的方法。不过有些差别:java中可以使用super调用父类中的任何公开的方法,但是在prototypejs里面$super只是一个方法,不是父对象。我们先研究下,prototypejs是如何做到$super,后面再看我们能不能改造它,让$super更像java中的super关键字。


简单说下prototypejs中的Object.extend(),功能其实和jQuery库中的$.extend(),underscore库_.extend()功能很类似,用来将一个对象上的属性和方法拷贝到另一个对象上。只不过jQuery将extend函数挂在了$对象上,underscore将extend挂在了_对象上,而prototypejs则将它挂在了Object对象上,这些类库都没有修改js内置函数的原型。看下prototypejs中object.js的注释:

 Because it is dangerous and invasive to augment `Object.prototype` (i.e.,
 add instance methods to objects), all these methods are static methods that
 take an [[Object]] as their first parameter.


知乎上的这篇文章讨论了“为什么不要直接在Object.prototype上定义方法?”。所以prototypejs也并没有修改Object.prototype,而是将Object相关的方法挂在了Object函数上。我们知道javascript中函数其实也是对象,也能添加自定义的方法或者属性。下面这段代码是完全正确的,prototypejs库也是采用了类似的方法,扩展了Object(这个既是函数,又是对象的东西)。

Object.plus = function(a,b){return a + b;};
Object.owner = "aty";
alert(Object.owner);
alert(Object.plus(1,2));

下面我们来看下Class.create()这个API的使用,主要是看懂API文档中的三段话:

第一段:

Class.create creates a class and returns a constructor function for instances of the class. Calling the constructor function (typically as part of a new statement) will invoke the class's initialize method.

这个so easy,不过还是补充一段代码一看就懂了。

var Base = Class.create({
	initialize : function(){
		console.log("i will be invoked when call new Base");
		for(var i = 0; i<arguments.length;i++)
		{
			console.log((i+1)+ " param="+arguments[i]);
		}
		
	}
});

new Base("a",1);

第二段:这个主要介绍create()函数参数的,有点复杂,我们慢慢分解。

Class.create accepts two kinds of arguments. If the first argument is a Class, it's used as the new class's superclass, and all its methods are inherited. Otherwise, any arguments passed are treated as objects, and their methods are copied over ("mixed in") as instance methods of the new class.

先看下面这段代码,会报错:Uncaught TypeError: Cannot read property ‘push’ of undefined。

// javascript中所谓的类就是函数
function MyParent(id)
{
	this.id = id;
}

MyParent.funcA = function(){
	console.log("funcA in object");
}

MyParent.prototype.funcB = function(){
	console.log("funcA in prototype");
}

var MyChild = Class.create(MyParent,{
	funcC : function(name){
		console.log("");
	}
});

我们自己创建了一个javascript类MyParent,希望用Class.create()实现继承,但是prototypejs却报错了。我们看class.js的一段源代码:

 function create() {
    var parent = null, properties = $A(arguments);
	//判断第一个参数是否是函数(也就是类)
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

	// 如果第一个参数是函数,会直接读取subclasses等属性
    if (parent) {
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }
	......
}

很显然第一个参数是函数,那么prototypejs就会读取它的subclasses等属性。很显然我们自定义的函数是没有subclasses属性的,而Class.create()返回的函数是有这些属性的。这提示我们:create()第一个参数如果是函数,那么必须要是Class.create()返回的函数,不能是我们自定义的函数。比如下面这段代码就不会报错了:

// create的第一个参数不是函数
var MyBase = Class.create({
		hi:function(){console.log("hi");}
	});

// 第一参数是Class.create()的返回值
var MyChild = Class.create(MyBase,{
		funcC : function(name){
			console.log("right");
		}
});

new MyChild().hi();
new MyChild().funcC();


我们再看一下create()第一个参数是objec时,会有什么效果。

var obj = {};
	
var AfterMixIn = Class.create(obj, {id:"123",name:"aty"},{
	funcC : function(name){
		console.log("name="+name);
	}
});

console.log(obj.id);//undefined

var newObject = new AfterMixIn(); 

newObject.funcC("aty");//name=aty

console.log("id=" + newObject.id);//id=123

//error: afterMixIn.funcC is not a function
AfterMixIn.funcC("aty");

输出结果已经放在上面的代码注释里了,可以看到:create()并不会修改第一个参数obj,而是返回了一个新函数(prototypejs中的类)AfterMixIn。类AfterMixIn(既是javascript中的函数,也是对象)本身并没有增加属性id、name和方法funcC,必需通过创建AfterMixIn类的对象来访问这些属性和方法。


上面这段代码稍微修改下,在chrome下debug,可以看到这些新增的id、name、funcC其实是存储在AfterMixIn这个函数的原型中的。

var AfterMixIn = Class.create({}, {id:"123",name:"aty"},{
	funcC : function(name){
		console.log("name="+name);
	}
});

var newObject1 = new AfterMixIn(); 
var newObject2 = new AfterMixIn(); 
console.log("id in newObject1="+newObject1.id);//123
console.log("id in newObject2="+newObject2.id);//123

AfterMixIn.prototype.id="update";

console.log("id in newObject1="+newObject1.id);//update
console.log("id in newObject2="+newObject2.id);//update

学习prototypejs中的继承实现机制(一): Object.extend()、Class.create()、Class#addMethods()的使用[通俗易懂]

第三段:主要讲$super怎么使用的,本文刚开始的代码就使用了$super,这个很容易不再赘述。

If a subclass overrides an instance method declared in a superclass, the subclass's method can still access the original method. To do so, declare the subclass's method as normal, but insert $super as the first argument. This makes $super available as a method for use within the function.


我们接着看下Class#addMethods()这个API的使用,它的功能很简单“To extend a class after it has been defined”。

var Animal = Class.create({
	initialize:function(name){
		this.name = name;
	}
	
});

var Person = Class.create(Animal,{
	say : function(msg){
		console.log(this.name + " say '" + msg + "'!");
	}
});
	

var po = new Person("aty");

Person.addMethods({
	smile : function(msg){
		console.log(this.name + " smile '" + msg + "'!");
	}
});

Person.prototype.anotherSmile = function(msg){
	console.log(this.name + " anotherSmile '" + msg + "'!");
}
	 
po.smile("learn is happy.");
po.anotherSmile("learn is happy.");

简单吧,Class#addMethods()其实就是将方法加入到了函数的原型中,被该构造函数创建的所有对象共享。

至此应该知道怎么使用Object.extend()、Class.create()、Class#addMethods()了。现在看官方的“Defining classes and inheritance”这篇tutorial文章应该不陌生了。
tutorial里面还提到了一个问题,需要关注:

var Logger = Class.create({
	  initialize: function() { },
	  log: [],
	  write: function(message) {
		this.log.push(message);
	  }
});
    
var logger = new Logger;
logger.write('foo');
logger.write('bar');


var another = new Logger();
console.log(another.log);//["foo", "bar"]


我们知道这些属性和方法都是放在javascript函数的prototype中的,会被构造函数创建的所有对象共享。由于javascript中原型读写的不对等性,会导致js也区分基本数据类型和引用数据类型。和上面代码类似,下面这段代码就不会有问题,不同Logger对象的count么有影响。

var Logger = Class.create({
  initialize: function() { },
  count: 0,
  write: function(message) {
    this.log.push(message);
  }
});
    
var logger = new Logger;
logger.count++;
logger.count++;
console.log(logger.count);//2

var another = new Logger();
console.log(another.count);//0


如果你对于javascript原型的读写不等性、原型的copy-on-write不太理解,可以看下我的另外2篇文章。

javascript原型的修改与重写(覆盖)差别
javascript读取和修改原型特别需要注意的事儿,因为原型的读写不具有对等性

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/163399.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月22日 下午11:16
下一篇 2022年7月22日 下午11:36


相关推荐

  • CSS 都有哪些选择器?

    CSS 都有哪些选择器?CSS 都有哪些选择器 1 标签选择器 如 body div p ul li 2 类选择器 如 class head class head logo 3 ID 选择器 如 id name id name txt 4 全局选择器 如 号 5 组合选择器 如 head head logo 注意两选择器用空格键分开 6 继承选择器 如 divp 注意两选择

    2026年3月17日
    3
  • maria和mysql_mysql为什么没有淘汰

    maria和mysql_mysql为什么没有淘汰mysql:driver驱动类为:com.mysql.jdbc.Driverurl为:jdbc:mysql://localhost:3306/testmariadbdriver驱动类为:org.mariadb.jdbc.Driverurl为:jdbc:mariadb://localhost:3306/test

    2025年7月9日
    3
  • idea 删除一行快捷键_excel删除一行快捷键

    idea 删除一行快捷键_excel删除一行快捷键以前习惯用eclipse,但是现在用了IDEA很不习惯,也不知道IDEA的快捷开发键。1)eclipse中删除一行是Ctrl+D,很方便,也很实用。2)IDEA中删除一行则是Ctrl+Y,如下图,这是什么设计,还好我手指长能够得到Y…3)当然了可以在IDEA中设置称eclipse快捷键,先用Ctrl+Alt+S打开IDEA的设置,在上面的地址栏中搜索keymap,如下图,在keymap设置中选择eclipse然后点击右下加apply,最后点击OK。…

    2025年9月30日
    8
  • ResNet介绍

    ResNet介绍ResNet 介绍 1 简要概括 ResNet ResidualNeur 由微软研究院的 KaimingHe 等四名华人提出 通过使用 ResNetUnit 成功训练出了 152 层的神经网络 并在 ILSVRC2015 比赛中取得冠军 在 top5 上的错误率为 3 57 同时参数量比 VGGNet 低 效果非常突出 ResNet 的结构可以极快的加速神经网络的训练 模型的准确率也有比较大的提

    2026年3月26日
    3
  • goland2021.2激活破解(已测有效)

    goland2021.2激活破解(已测有效),https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月14日
    305
  • 什么是文本挖掘 ?「建议收藏」

    什么是文本挖掘 ?「建议收藏」什么是文本挖掘  文本挖掘是抽取有效、新颖、有用、可理解的、散布在文本文件中的有价值知识,并且利用这些知识更好地组织信息的过程。1998年底,国家重点研究发展规划首批实施项目中明确指出,文本挖掘是“图像、语言、自然语言理解与知识挖掘”中的重要内容。  文本挖掘是信息挖掘的一个研究分支,用于基于文本信息的知识发现。文本挖掘利用智能算法,如神经网络、基于案例的推理、可能性推理等,并结合文字处

    2022年6月17日
    29

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号