1、封装
他是基于对象(object-based)的语言,但并不是面向对象(OOP)的,因为他本身没有类(enm。。。现在的语法有class了吧)。
我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象:
1.1、原始模式
// 这是一个原型对象
var Cat = {
name : '',
color : ''
}
// 每次生成实例都得这么写,太麻烦;实例和原型之间没有任何方法,看不出联系
var cat1 = {}; // 创建一个空对象
cat1.name = "大毛"; // 按照原型对象的属性赋值
cat1.color = "黄色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";
1.2、改进,解决代码重复问题
// 但两个cat之间没有联系,看不出是同一个原型对象的实例???(为啥看不出呀)
function Cat(name,color) {
return {
name:name,
color:color
}
}
var cat1 = Cat("大毛","黄色");
var cat2 = Cat("二毛","黑色");
1.3、构造函数模式
// 内部使用了this,对构造函数使用`new`运算符,就能生成实例,并且`this`变量会绑定在实例对象上。
function Cat(name,color){
this.name=name;
this.color=color;
// 构造函数模式的弊端
// 每次生成一个实例都会生成重复的内容,浪费内存
this.type = "猫科动物";
this.eat = function(){alert("吃老鼠");};
}
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
// 自动含有 constructor 属性,指向构造函数
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
// instanceof 运算符,验证原型对象与实例对象之间的关系。
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true
1.4、原型模式
每个构造函数都有一个 prototype 属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function(){alert("吃老鼠")};
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.type); // 猫科动物
cat1.eat(); // 吃老鼠
// 所有实例的type属性和eat()方法都是同一个内存地址,指向prototype对象
alert(cat1.eat == cat2.eat); //true
1.5、Prototype模式的验证方法
isPrototypeOf():
alert(Cat.prototype.isPrototypeOf(cat1)); //true
hasOwnProperty():
alert(cat1.hasOwnProperty("name")); // true
in运算符:
// 判断某个实例是不是含有某个属性,不管是不是本地属性
alert("name" in cat1); // true
// 遍历某个对象的所有属性
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
2、构造函数继承
// 要让猫继承动物
function Animal(){
this.species = "动物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
对象之间继承的 5 种方式:
2.1、构造函数绑定
使用call或apply方法,将父对象的构造函数绑定在子对象上
function Cat(name,color){
// 使用call或apply方法,将父对象的构造函数绑定在子对象上
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
2.2、原型模式
Cat.prototype = new Animal(); // 将猫的原型指向了一个新的动物对象
// 上一行完全删除了Cat原型对象原先的值,任何一个原型对象和他的实例都有一个constructor属性,指向他的构造函数,如果不加上面那句,cat的构造函数就变成了Animal,而他其实是用Cat生成的,继承链混乱。
// 同样的,如果替换了原型对象,那么也需要指回来
// o.prototype = {};
// o.prototype.constructor = o;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
2.3、直接继承prototype
function Animal(){ }
Animal.prototype.species = "动物";
// 效率高,但是猫和动物的原型都指向了同一个对象,对猫的修改会影响到动物
Cat.prototype = Animal.prototype;
// 这句话把动物的原型也给改了。。变成Cat了
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
2.4、空对象做继承
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
alert(Animal.prototype.constructor); // Animal
2.5、拷贝继承:把父对象的所有属性和方法,拷贝进子对象
function Animal(){}
Animal.prototype.species = "动物";
// 用作属性拷贝的函数
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黄色");
alert(cat1.species); // 动物
3、非构造函数继承(两个普通对象继承)
var Chinese = {
nation:'中国'
};
var Doctor ={
career:'医生'
}
要让医生去继承中国人,也就是获得一个中国医生,三种方法
3.1、object()方法
// 把子对象的prototype属性指向父对象,从而联系起来
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var Doctor = object(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); //中国
3.2、浅拷贝(把父对象的全部属性拷贝给子对象)
function extendCopy(p) {
var c = {};
// 注意是浅拷贝,只拷贝了引用,当子对象的属性变化时,父对象的对应属性会发生同样的变化
for (var i in p) {
c[i] = p[i];
}
// “uber”是源某些人在模拟class时用来表示super的(因为百super是关键字所以不能直接度用)。
c.uber = p;
return c;
}
var Doctor = extendCopy(Chinese);
Doctor.career = '医生';
alert(Doctor.nation); // 中国
3.3、深拷贝(jQuery库用的是这种继承方式)
// 递归调用浅拷贝
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
var Doctor = deepCopy(Chinese);
Chinese.birthPlaces = ['北京','上海','香港'];
Doctor.birthPlaces.push('厦门');
// 这样父对象没有受到影响
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门
alert(Chinese.birthPlaces); //北京, 上海, 香港
enm。。。关于基本类型和对象类型的存储方式有待复习???
4、继承机制的设计思想
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
new命令引入了Javascript,用来从原型对象生成一个实例对象
new命令后面跟的不是类,而是构造函数
用构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。
5、Js定义类的三种方法
5.1、构造函数:比较复杂,用到this、prototype
5.2、Object.creat():不能实现私有属性和私有方法,实例对象之间也不能共享数据,对"类"的模拟不够全面。
5.3、极简主义法:在构造函数里定义实例对象,并返回它
优点:
容易理解,结构清晰优雅,符合传统的"面向对象编程"的构造;
让一个类继承另一个类,实现起来很方便。只要在前者的createNew()方法中,调用后者的createNew()方法即可;
只要不是定义在对象上的方法和属性,都是私有的,只能通过共用方法来调用
var Cat = {
createNew: function(){
var cat = {};
var sound = "喵喵喵";
cat.makeSound = function(){ alert(sound); };
return cat;
}
};
还可以数据共享:
var Cat = {
sound : "喵喵喵", // 这里是共享数据,当一个对象改变只后,另一个对象也会变
createNew: function(){
var cat = {};
cat.makeSound = function(){ alert(Cat.sound); };
cat.changeSound = function(x){ Cat.sound = x; };
return cat;
}
};