类理论
封装:强调数据和操作数据的行为本质上是相互关联的。好的设计就是将数据以及和它相关的行为封装(或者打包)起来。
实例化:我们可以使用类对数据结构进行分类。可以把任意数据结构看作范围更广的定义中的一种实例。如“汽车”为“交通工具”的一种实例。
继承:子类通过继承父类来对类进行相应的扩展。
多态:父类的通用行为可以被子类用更加特殊的行为重写。
注意:类理论强调父类和子类使用相同的方法名来表示特定的行为,从而让子类重写父类。但在JS代码中这样会降低代码的可读性和健壮性。
JS中的类理论
由于类是一种设计模式,在JS中可以通过一些方法实现类的功能。JS中提供了一些近似类的语法,如 new 、class等关键字。但这实质中只是一种语法糖。
类构造函数:是一种初始化一个类实例初始化所需要的所有信息的一种方法,这个方法名通常与类名相同,被称为构造函数。
class Foo{
private int a;
//构造函数
Foo(int a){
this.a = a
}
public void methods()
System.out.println("some methods");
}
public static void main(String[] args){
Foo foo = new Foo("24"); //实例化一个类
}
}
类的继承: 定义一个类,然后定义一个继承前者的类。每一个继承而来的类都是一个独一无二的存在。
class Bar extends Foo{
public void main(String[] args){
Bar bar = new Bar();
bar.methods(); //调用继承于父类的方法。
}
}
类的多态:任何方法都可以引用继承层次中高层的方法。在继承链的不同层次中一个方法名可以被多次定义,当调用方法时会自动选择合适的定义。
class Baz extends Bar{
public void methods(){
System.out.println("my methods defined by myself");
}
public static void main(String[] args){
Baz baz = new Baz(); //my methods defined by myself
}
}
混入和多重继承:JS中没有提供多重继承的机制。在继承或者实例化时,JS的对象机制并不提供自动执行复制行为。简单来说,JS中只有对象,并不存在可以被实例化的“类”,一个对象并不会被复制到其他对象,它们只是被关联起来。JS中使用方法来模拟类的复制行为。
显示混入
function mixin(sourceObj, targetObj){
for(var key in sourceObj){
if(!key in targetObj){
targetObj[key] = sourceObj[key];
}
}
}
隐式混入
function SupType(name,age){
this.name = name;
this.age = age;
}
function SubType(name,age,job){
SupType.call(this,name,age);
this.job = job;
}
var o = new SubType("Fzw","23","coder");
console.log(o); // {name:"Fzw",age:23,job:"coder"}
原型
定义:JS对象中有一个特殊的[[prototype]]内置属性,实质上是对其他对象的引用。几乎所有的对象在创建时[[prototype]]都会被赋予一个非空的值。当在对象中查找某一个属性的值时,会首先在对象本身上查找,否则会在对象的[[prototype]]进一步查找。这个过程会持续到找到匹配的属性名或者整条[[prototype]]被查询完。如果是后者,则返回undefined。
[[prototype]]的尽头:所有普通的[[prototype]]链最终都会指向内置的Object.prototype。
原型继承
SubType.prototype = SupType.prototype
这个等式并不会创建一个关联到SupType的新对象,它只是让SubType.prototype去直接引用SupType.prototype,当对SubType.prototype进行任何修改增删操作时,都直接修改SupType.prototype本身。
SubType.prototype = new SupType()(存在副作用的做法!)
这种做法确实会创建一个关联到SupType.prototype的新对象。但是调用了SupType的构造函数,其存在的一些具体副作用的方法如:修改状态、注册到其他对象或给this添加属性等。会间接影响SubType 的实例。
SubType.prototype=Object.create(SupType.prototype) Object.setPrototypeOf(SubType.prototypr, SupType.prototype)
两者实现的结果几乎一致,创建了一个无副作用且关联到SubType.prototype的新对象。区别在于前者使用于ES6之前,且存在一些性能上的损失,抛弃的对象需要进行垃圾回收。后者在ES6后可用。
检查类的关系
假设有对象Foo,如何寻找Foo对象委托的对象(假设存在)。
function Foo(){}
Foo.prototype.name = "Foo";
function Bar(){}
Bar.prototype = Object.create(Foo.prototype);
function Baz(){}
Baz.prototype = Object.create(Bar.prototype);
var foo = new Foo();
var bar = new Bar();
var baz = new Baz();
console.log(bar instanceof Foo) //true
console.log(baz instanceof Bar) //true
console.log(baz instanceof Foo) //true
//instanceOf 只能判断在foo的原型链中是否存在指向Foo.prototype的对象
使用函数方法判断[[prototype]]的反射问题
Foo.prototype.isPrototypeOf(bar); // true
Bar.prototype.isPrototypeOf(baz); // true
Foo.prototype.isPrototypeOf(baz); // true
//和instanceof的效果一样
//使用Object.getPrototypeOf , 因代码的执行环境有差异
Object.getPrototypeOf(foo) == Foo.prototype;//true
Object.getPrototypeOf(bar) == Foo.prototype;//false
Object.getPrototypeOf(baz) == Foo.prototype;//false
//使用__proto__属性(绝大对数支持一种非标准的方法来访问内部[[prototype]]属性)
foo.__proto__ === Foo.prototype; //true
bar.__proto__ === Foo.prototype; //false
baz.__proto__ === Foo.prototype; //false
//__proto__大致实现
Object.defineProperty(Object.prototype,"__proto__",{
get:function(){
return Object.getPrototypeOf(this);
},
set:function(o){
Object.setPrototypeOf(this, o);
return this;
}
});
关于Object.create
//ES5新增函数,ES6polyfill如下,实现Object.create的部分功能
if(!Object.create){
Object.create = function(o){
function F(){}
F.prototype = o;
return new F();
}
}
//实际上,Object.create还可以在对象添加属性,并配置访问属性
var obj = {
a:24
}
var newObj = Object.create(obj, {
b:{
enumerable:false,
writable:true,
configurable:false,
value:3
},
a:{
enumerable:true,
writable:false,
configurable:false,
value:4
}
});
newObj.hasOwnProperty("a"); //true
newObj.hasOwnProperty("b"); //true
newObj.hasOwnProperty("c"); //false
console.log(newObj) //{a:3,b:4}
原型继承本质:委托
原型链机制的本质是指向对象中的一个内部链接引用另一个对象。如果在第一个对象中没有查找到相应的属性或方法引用,那么JS引擎就会继续在[[prototype]] 关联的对象上进行查找。同理,如果后者也没有找到需要的引用就会继续查询它的[[prototype]],以此类推,这一系列对象被称为原型链。