浅谈prototype、__proto__和继承
在谈及prototype、__proto__和继承之前,先说说js对象
1. js对象
1.1 new(当然不仅仅new可以创建一个对象,var obj = {a = "a", b = "b"};也是一个对象)
js的new类似于Java,new操作相当于3个操作:
(1)在系统堆中申请一个实例空间,实例空间存放该类的所有非静态成员,若无非静态成员,则申请一段空间,像java的ArrayList一样有初始空间;
(2)在系统堆栈中申请一个指针字节空间,将步骤一申请的实例空间首地址存入该指针字节空间,该指针又被称为this;
(3)对被实例化对象的非静态成员通过this赋初值,相当于将自身类当做函数使用;
1.2 对象的本质是键值对集合
相关js代码如下:
var arr = []; //相当于创建了一个数组类型的对象arr
console.log("arr.length", arr.length);
arr["1"] = 1; //该对象为数组类的对象,故被解释为arr[1],下标0,1两个元素,故length=2;
console.log("arr.length", arr.length);
arr["a"] = 2; //相当于arr.a,给arr对象增加了一个成员a
console.log("arr.a", arr.a);
console.log("arr.length", arr.length);
输出如下
表明插入a时长度未变化
var obj = new Object;
obj.2 = 2; //语法错误,表明对象.成员,"obj.2"相当于给对象put一个键,键需满足变量名命名原则,键需为String类型,故语法错误,从而推断出对象相当于键值对集合
2. js对“对象.成员”的解释
对于"对象.成员"的解释,浏览器有两种完全不同的方式:左值方式和表达式方式。
2.1、左值方式:首先检查相关成员是否在本对象的键值对集合中存在,
若存在,则更改,结束!
若不存在,则创建该键值对,结束!
2.2、表达式方式:首先检查相关成员是否在本对象的键值对集合中存在,
若存在,则输出,结束!
若不存在,则沿着__proto__(原型链),在原型的键值对集合中查找,直到找到根集合(Object的prototype),
若依然找不到,则输出undefined,结束; 若有任何一次找到,则输出,结束!
进入正题:
3. prototype(原型)&__proto__(原型链)
3.1. prototype指向自身(包括构造方法和静态成员),仅类存在prototype,代码如下:
klass = function() {
this.member1 = "member1";
console.log("成功输出");
}
klass.prototype.member = "hello";
console.log(klass.prototype);
console.log(window);
var klass = function() {
this.member1 = "member1";this["member2"] = "member2";
console.log("成功输出");
}
klass.prototype.constructor();
表明prototype必含constructor()函数,该函数指向自身函数;
该函数指向自身函数是什么意思呢?,3.2予以解答
3.2类类型含有原型prototype和原型链__proto__,对象仅含有__proto__, __proto__为“隐含”属性,__proto__可以更改对象所属类型,但以下划线开头的函数及对象是系统内部函数,不应在应用开发时使用
一个类的原型链所对应的原型是判断instanceof的唯一标识,即a instanceof b; 若a的原型链指向的原型为b的原型,则返回true
类的原型就是prototype所指向的内容;而对象的__proto__所指向的空间,就是其所属类型的prototype所指向的空间!
js中的任何类有以下3层含义:
(1)函数(可被直接调用)
(2)对象(键值对集合)
(3)类(原型所指对象必然存在constructor(),该构造函数指向该函数本身)。
即,constructor()指向的是该类作为函数的应用;该类prototype是作为该类的对象,该类对象的__proto__也是该类的对象。
图解:
代码如下:
var klass = function() {}
var obj1 = new klass;
var object1 = new Object;
obj1.__proto__ = object1.__proto__;
console.log("obj1.__proto__ instanceof klass", obj1.__proto__ instanceof klass);
console.log("klass.prototype instanceof Function:", klass.prototype instanceof Function);
console.log("klass.prototype instanceof Object:", klass.prototype instanceof Object);
输出如下:
3.3. 修改prototype成员(“静态成员”)的值见代码
var klass = function() {
}
klass.prototype.member1 = "member1";
var obj1 = new klass;
obj1.member1 = 100;
console.log("obj1.member1:", obj1.member1);
运行结果如下:
出人意料的是,上面输出的并不是100,而是member1;这不能说明:member1不是“静态成员”!
实际上,obj1.member1 = 100;的操作过程是:
1、检查obj1是否存在"member1"关键字;
2、上述检查发现,obj1本身的键值对集合中,不存在关键字"member1";
3、于是,创建键值对:member1 -> 100!
但是,ob1的原型链(__proto__)所指键值对集合中也存在member1!这个member1才是真正的“静态成员”!
也就是说,如果想更改“静态成员”的值,不应该直接通过对象进行!而应该通过“类”进行!这也符合Java原则!
在js中,如是引用和更改“静态成员”的值:
klass.prototype.member1 = 100;
运行结果如下:
4.经过上面的理解:所谓继承,就是将子类的prototype的__proto__由指向Object的prototype更改为指向其父类的prototype,但上面说__proto__属性不建议用户使用,所以,更改子类的prototype为父类的prototype。
相关继承代码如下:
function Extends (baseClass, currentClass) {
var Tmp = function () {};
Tmp.prototype = baseClass.prototype;
currentClass.prototype = new Tmp;
currentClass.prototype.constructor = currentClass; //切记不要忘记恢复构造函数
};
var Animal = function (name) {
this.name = name;
};
function Dog (name, wang) {
Animal.call(this, name);
this.wang = wang;
};
Extends(Animal, Dog);
var dog = new Dog("犬类", "土狗");
console.log(dog);
console.log("dog instanceof Dog:", dog instanceof Dog);
console.log("dog instanceof Animal:", dog instanceof Animal);
输出如下: