面向对象与原型
ECMAScript 有两种开发模式:1.函数式(过程化),2.面向对象(OOP)。面向对象的语言
有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但
是,ECMAScript 没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
创建一个对象,然后给这个对象新建属性和方法。
var box = new Object(); //创建一个Object 对象 box.name = 'Lee'; //创建一个name 属性并赋值 box.age = 100; //创建一个age 属性并赋值 box.run = function () { //创建一个run()方法并返回值 return this.name + this.age + '运行中...'; }; alert(box.run()); //输出属性和方法的值 上面创建了一个对象,并且创建属性和方法,在run()方法里的this,
//this表示new Object()实例化出来的那个对象
//this要放在一个作用域下,比如box.run() {} ,这个是box作用域下的方法,方可用this,来表示box本身
var name1 = 'jack'; alert(this.name1); //这里的this代表的是window
var box = new Object(); box.name = 'Lee'; box.age = 100; box.run = function () { return this.name + this.age + '运行中...'; }; var box2 = box; box2.name = 'Jack'; box2.age = 200; box2.run = function () { return this.name + this.age + '运行中...'; }; alert(box.run()); alert(box2.run());
一、工厂模式 这种方法为了解决实例化对象产生大量重复的代码问题
可以传参
//工厂模式 function createObject(name, age) { var obj = new Object(); //创建对象 obj.name = name; //添加属性 obj.age = age; obj.run = function () { //添加方法 return this.name + this.age + '运行中...'; }; return obj; //返回对象引用 }; function createObject2(name, age) { var obj = new Object(); //创建对象 obj.name = name; //添加属性 obj.age = age; obj.run = function () { //添加方法 return this.name + this.age + '运行中...'; }; return obj; //返回对象引用 }; var box1 = createObject('Lee', 100); //创建第一个对象 var box2 = createObject('Jack', 200); //创建第二个对象 var box3 = createObject2('kkk', 500); //创建第三个对象 //alert(box1.run()); //打印第一个对象实例的run()方法 //alert(box2.run()); //打印第二个对象实例的run()方法 //alert(typeof box1); //alert(typeof box2); alert(box1 instanceof Object); alert(box2 instanceof Object); alert(box3 instanceof Object); //不管怎样,他们都是Object类型,就无法区分,谁到底是谁的对象了
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
搞清楚他们到底是哪个对象的实例。(识别对象引用)
二、构造函数的方法
function Box(name, age) { //构造函数模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + '运行中...'; }; } var box1 = new Box('Lee', 100); //new Box()即可 var box2 = new Box('Jack', 200);
function Desk(name, age) { //创建一个对象,所有构造函数的对象其实就是Object this.name = name; //添加一个属性 this.age = age; this.run = function () { //添加一个方法 return this.name + this.age + '运行中...'; }; }; //1.构造函数没有new Object,但它后台会自动var obj = new Object //2.this就相当于obj //3.构造函数不需要返回对象引用,它是后台自动返回的 //1.构造函数也是函数,但函数名第一个字母大写 //2.必须new 构造函数名(),new Box(),而这个Box第一个字母也是大写的 //3.必须使用new 运算符 var box2 = new Box('Jack', 200); //实例化 var box3 = new Desk('kkk', 500); //实例化
//alert(box1.run());
//alert(box2.run());
//alert(box1 instanceof Object);
//alert(box1 instanceof Box);
//alert(box2 instanceof Box);
//alert(box3 instanceof Box); //可以识别了,因为box3是Desk对象的引用
alert(box3 instanceof Desk); //可以识别
var user = 'bbb';
alert(this.user); //window,name变量比较特殊
function box() { //普通函数,首字母无须大写
}
alert(Box('Lee', 100)); //构造函数,用普通函数调用一般是无效的,必须使用new运算符
对象冒充
o冒充Box,即有Box的run()方法
var o = new Object(); Box.call(o, 'Lee', 100); //对象冒充 alert(o.run());
两个实例化后的属性或方法是否相等。
//alert(box1.name == box2.name); //alert(box1.age == box2.age); //alert(box1.run() == box1.run()); //构造函数体内的方法的值是相当的 //alert(box1.run == box2.run); //因为他们比较的是引用地址,
function Box(user, age) { //创建一个对象,所有构造函数的对象其实就是Object this.user = user; //添加一个属性 this.age = age; this.run = run; }; 把构造函数里的方法(或函数)用new Function()方法来代替,得到一样的效果,更加证明,他们最终判断的是引用地址,唯一性 function run() { //把构造函数内部的方法通过全局来实现引用地址一致 return this.user + this.age + '运行中...'; } var box1 = new Box('Lee', 100); //实例化后地址为1 var box2 = new Box('Lee', 100); //实例化后地址为2 //alert(box1.run == box2.run); // alert(box1.run()); alert(run());
PS:虽然使用了全局的函数run()来解决了保证引用地址一致的问题,但这种方式又带来了一个新的问题,全局中的this 在对象调用的时候是Box 本身,而当作普通函数调用的时候,this 又代表window。
三、对象原型
prototype(原型属性)这个属性是对象,用途是包含可以由特定类型的所有实例共享的属性和方法。
//构造函数 function Box(name, age) { this.name = name; //实例属性 this.age = age; this.run = function () { //实例方法 return this.name + this.age + '运行中...'; }; } //原型 function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name = 'Lee'; //原型属性 Box.prototype.age = 100; //原型属性 Box.prototype.run = function () { //原型方法 return this.name + this.age + '运行中...'; }; var box1 = new Box(); var box2 = new Box(); //alert(box1.name); //alert(box1.run()); //如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的 //如果是原型方法,那么他们地址是共享的,大家都是一样 alert(box1.run == box2.run); alert(box1.prototype); //这个属性是一个对象,访问不到 alert(box1.__proto__); //这个属性是一个指针指向prototype原型对象 alert(box1.constructor); //构造属性,可以获取构造函数本身 //作用是被原型指针定位,然后得到构造函数本身 //其实就是对象实例对应的原型对象的作用 //判断一个对象实例(对象引用)是不是指向了原型对象,基本上,只要实例化了,他自动指向的 alert(Box.prototype.isPrototypeOf(box1)); var obj = new Object(); alert(Box.prototype.isPrototypeOf(obj)); var box2 = new Box(); alert(box2.name); //实例属性不会共享,所以box2访问不到实例属性,就只能访问原型 box1.name = 'Jack'; //实例属性,并没有重写原型属性 alert(box1.name); //就近原则 delete box1.name; //删除实例中的属性 //delete Box.prototype.name; //删除原型中的属性 //Box.prototype.name = 'KK'; //覆盖原型中的属性 alert(box1.name); box1.name = 'Kac'; alert(box1.hasOwnProperty('name')); //判断实例中是否存在指定属性 alert('name' in box1); //不管实例属性或原型属性是否存在,只要有就返回true,两边都没有,返回false box1.name = ''; alert(isProperty(box1, 'name')); //判断只有原型中有属性, function isProperty(object, property) { return !object.hasOwnProperty(property) && (property in object) } */ //原型 function Box() {} //构造函数函数体内什么都没有,这里如果有,叫做实例属性,实例方法 Box.prototype.name = 'Lee'; //原型属性 Box.prototype.age = 100; //原型属性 Box.prototype.run = function () { //原型方法 return this.name + this.age + '运行中...'; }; var box1 = new Box();
实例化以后自动创建了Box的prototype对象,__proto__是prototype对象的指针,
原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__
属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor。
通过这两个属性,就可以访问到原型里的属性和方法了。
PS:IE 浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器
均能识别。虽然可以输出,但无法获取内部信息。
alert(box1.__proto__); //[object Object]
判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向
原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原
型中的值。
如何判断属性是在构造函数的实例里,还是在原型里?可以使用hasOwnProperty()函数
来验证:
alert(box.hasOwnProperty('name')); //实例里有返回true,否则返回false
in操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原
型中。
我们可以通过 hasOwnProperty()方法检测属性是否存在实例中,也可以通过 in来判断
实例或原型中是否存在属性。那么结合这两种方法,可以判断原型中是否存在属性。
function isProperty(object, property) { //判断原型中是否存在属性
return !object.hasOwnProperty(property) && (property in object);
}
var box = new Box();
alert(isProperty(box, 'name')) //true,如果原型有
四、原型自变量
很好的体现的封装性
//使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{} Box.prototype = { constructor : Box, //强制指向Box name : 'Lee', age : 100, run : function () { return this.name + this.age + '运行中...'; } };
使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,
字面量创建的方式使用constructor 属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
var box = new Box(); alert(box instanceof Box); alert(box instanceof Object); alert(box.constructor == Box); //字面量方式,返回false,否则,true alert(box.constructor == Object); //字面量方式,返回true,否则,false 如果想让字面量方式的constructor 指向实例对象,那么可以这么做: Box.prototype = { constructor : Box, //直接强制指向即可 };
function Box() {}; Box.prototype = { //原型被重写了 constructor : Box, name : 'Lee', age : 100, run : function () { return this.name + this.age + '运行中...'; } }; Box.prototype = { age = 200 }; var box = new Box(); //在这里声明 alert(box.run()); //box 只是最初声明的原型
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。
function Box() {}; Box.prototype = { //原型被重写了 constructor : Box, name : 'Lee', age : 100, run : function () { return this.name + this.age + '运行中...'; } }; Box.prototype = { age = 200 }; var box = new Box(); //在这里声明 alert(box.run()); //box 只是最初声明的原型
原型对象不仅仅可以在自定义对象的情况下使用,而ECMAScript 内置的引用类型都可
以使用这种方式,并且内置的引用类型本身也使用了原型。
alert(Array.prototype.sort); //sort 就是Array 类型的原型方法 alert(String.prototype.substring); //substring 就是String 类型的原型方法 String.prototype.addstring = function () { //给String 类型添加一个方法 return this + ',被添加了!'; //this 代表调用的字符串 }; alert('Lee'.addstring()); //使用这个方法