私有变量和函数
在函数内部定义的变量和函数如果不对外提供接口,外部是无法访问到的,也就是该函数的私有变量和函数。
<script type="text/javascript"> function Box() { var color = "blue"; //私有变量 var fn = function() {}; //私有函数 } var obj = new Box(); alert(obj.color); //undefined alert(obj.fn); //undefined </script>
静态变量和函数
当定义一个函数后通过点“.”为其添加的属性和函数,通过对象本身仍然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数。
<script type="text/javascript"> function Obj() {}; Obj.num = 72; //静态变量 Obj.fn = function() {}; //静态方法 alert(Obj.num); //72 alert(typeof Obj.fn); //function var test = new Obj(); alert(test.num); //undefined alert(typeof test.fn); //undefined </script>
实例变量和函数
在面向对象编程中除了一些库函数我们还是希望在对象定义的时候同时定义一些属性和方法,实例化后可以访问,js也能做到这样
<script type="text/javascript"> function Box () { this.a = 1; this.fn = function() {}; } alert(Box.a); //undefined alert(typeof Box.fn); //undefined var box = new Box(); alert(box.a); //1 alert(typeof box.fn); //function </script>
为实例变量和方法添加新的方法和属性
<script type="text/javascript"> function Box () { this.a = 1; this.fn = function() {}; } var box1 = new Box(); box1.a = 2; box1.fn = {}; console.log(box1.a); //2 console.log(typeof box1.fn); //Object var box2 = new Box(); console.log(box2.a); //1 console.log(typeof box2.fn); //function </script>
在box1中修改了a和fn,而在box2中没有改变,这就说明box1中的属性和方法与box2中的属性和方法虽然同名但却不是同一个引用,而是对Box对象定义的属性和方法的一个复制。
这个对属性来说没有什么问题,但是对于方法来说问题就大了,因为方法都是在做完全一样的功能,但是却又分成两份复制,如果一个函数对象有上千个方法,那么他的每一个实例都要保持一份上千个
方法的复制,这显然照成了内存的极大浪费,这个怎么办呢?---prototype应运而生。
prototype基本概念
我们创建的每个函数都有一个prototype属性,这个属性是一个指针变量,指向一个对象,这个对象的用途包含可以有特定类型的所有实例共享的属性和方法。那么,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
使用原型的好处是可以让对象实例共享他所包含的属性和方法。也就是说,不必在构造函数中添加定义对象信息,而是可以直接将这些信息添加到原型中。使用构造函数的主要问题就是每个方法都要在实例中创建一遍
在javascript中,一共有两种类型的值,原始值和对象值。每个对象都有一个内部属性prototype,通常称之为原型。
原型的值可以是一个对象,也可以是null。如果它的值是一个对象,则这个对象也一定有自己的原型。这样就形成了一条线性的链,称之为原型链。
prototype含义
函数可以用来做构造函数来使用。另外只有函数才有prototype属性并且可以访问到,但是对象实例不具有该属性,只有一个内部的不可访问的__proto__属性。__proto__是对象中一个指向相关原型的神秘链接。
按照标准,__proto__是不对外公开的,也就是说这是个私有属性。
<script type="text/javascript"> var Browser = function() {}; Browser.prototype.run = function () { console.log("ok"); } var bro = new Browser(); bro.run(); </script>
当调用Bro.run()方法时,由于bro中没有这个方法,所以,他就会去他的__proto__中找,也就是Browser.prototype,所以最终执行了该run()方法。(函数首字母大写代表构造函数,区分普通函数)
当调用构造函数创建一个实例的时候,实例内部将包含一个内部指针变量(__proto__)指向构造函数的prototype,这个链接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间
<script type="text/javascript"> function Person(name) { this.name = name; } Person.prototype.printName = function() { console.log(this.name); } var perseon1 = new Person('gudoumaoning'); console.log(perseon1.__proto__); //Person.prototype FireFox可以访问到对象的__proto__ console.log(perseon1.constructor); //Person构造函数 console.log(Person.prototype); //Person.prototype
var person2 = new Person('cnblogs'); </script>
Person的实例person1中包含了name属性,同时自动生成一个__proto__属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法
每个Javascript函数都有prototype属性,这个属性引用了一个对象,这个对象就是原型对象。原型对象初始化的时候是空的, 我们可以在里面自定义任何方法和属性,这些方法和属性都被 构造函数所创建的对象继承
构造函数、实例和原型对象的区别
实例就是通过构造函数创建的。实例一创造出来就具有constructor属性(指向构造函数)和__proto__属性(指向原型对象)
构造函数中有一个prototype属性,这个属性是一个指针变量,指向他的原型对象。
原型对象内部也有一个指针变量(constructor属性)指向构造函数:Person.prototype.constructor = Person;
实例可以范文原型对象上定义的属性和方法。
在这里person1和person2就是实例,prototype是他们的原型对象。
再举个例子:
<script type="text/javascript"> function Animal(name) { this.name = name; } Animal.prototype.behavior = function() { //给基类构造函数的prototype添加方法 console.log("this is a " + this.name); } var Dog = new Animal("dog");//创建Dog对象 var Cat = new Animal("cat");//创建Cat对象 Dog.behavior(); //this is a dog Cat.behavior(); //this is a cat console.log(Dog.behavior == Cat.behavior); //true </script>
数组对象实例
再看个数组对象实例。当我们创建出array1这个对象的时候,array1实际上在javascript引擎中的对象模型如下:
var array1 = [1 ,2 ,3];
array1对象具有一个length属性值为3,但是我们可以通过如下方法来为array1增加元素:
array1.push(4);
push这个方法来自于array1的__proto__成员指向对象的一个方法(Array.prototype.push()).正是因为所有的数组对象都包含有一个指向同一个具有push,reverse等方法对象的(Array.prototype)的
__proto__成员,才使得这些数组对象可以使用push,reverse等方法。
函数对象实例
function Base() { this.id = id; }
var obj = new Base();
这样代码的结果是什么,在javascript引擎中看到的对象模型是:
原型链
原型链:当从一个对象那里调取属性或方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找,如果prototype没有,就会去prototype关联的前辈prototype那里寻找,如果再没有则继续查找prototype.prototype引用的对象,以此类推,直到prototype...prototype为undefined(Object的prototype解释undefined)从而形成了所谓的“原型链”。
<script type="text/javascript"> function Shape() { this.name = "shape"; this.toString = function() { return this.name; } } function TwoShape() { this.name = "2 shape"; } function Triangle(side, height) { this.name = "Triangle"; this.side = side; this.height = height; this.getArea = function() { return this.side * this.height/2; } } TwoShape.prototype = new Shape(); Triangle.prototype = new TwoShape(); TwoShape.prototype.constructor = TwoShape; Triangle.prototype.constructor = Triangle; var my = new Triangle(5, 10); console.log(my.getArea()); //25 console.log(my.toString()); //Triangle console.log(my.constructor);//Triangle(side, height) </script>
原型继承
原型继承:在原型链的末端,就是Object构造函数prototype属性指向的那个原型对象。这个原型对象是所有对象的祖先,这个老祖宗实现了诸如toString等所有对象天生就该具有的方法。其他内置构造函数,如
Function,Boolean,String,Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的特征。
ECMAScript中,实现继承的方法就是依靠原型链来实现的。
<script type="text/javascript"> function Box() { //被继承的函数叫做超类型(父类,基类) this.name = "Jack"; } function Tree() { //继承的函数叫做子类型(子类,派生类) this.age = 300; } Tree.prototype = new Box(); //通过原型链继承,赋值给子类型的原型属性 var tree = new Tree(); console.log(tree.name); //Jack </script>
原型链的问题:原型链虽然强大,可以用它来实现继承,但他也存在一些问题。其中最主要的问题来自包含引用类型的值原型。包含引用类型的原型属性会被所有实例共享;而这也正是为什么要在构造函数中
,而不是原型对象中定义属性的原因。在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就变成了原型的属性。
在创建子类型的实例时,不能向超类型的构造函数中传递参数,再加上刚刚讨论的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。
再举个例子:
<script type="text/javascript"> function Person(name) { this.name = name; //设置对象属性 } Person.prototype.company = "Microsoft"; //设置原型的属性 Person.prototype.SayHello = function() {//设置原型的方法 console.log("Hello, I'm " + this.name + "of" + this.company); } var BillGates = new Person("BillGates");//创建person对象 BillGates.SayHello(); //继承了原型的内容,输出”Hello,I'm BillGates of Microsoft“ var Jobs = new Person("Jobs"); Jobs.company = "Apple";//设置自己的company属性,覆盖了原型的company属性 Jobs.SayHello = function() { console.log("Hi," + this.name + "like" + this.company); } Jobs.SayHello(); //自己覆盖的属性和方法,输出”Hi,Jobs like Apple“ BillGates.SayHello();//Jobs的覆盖没有影响原型。 </script>
看下面一个原型链的例子:
<script type="text/javascript"> function Year() { this.value = 21; } Year.prototype = { method:function(){} }; function Hi() {}; Hi.prototype = new Year(); Hi.prototype.year = 'Hello World'; Hi.prototype.constructor = Hi; var test = new Hi(); //原型链 test [Hi的实例] Hi.prototype [Year的实例] {year:'Hello World'} Year.prototype {method:...} Object.prototype {toString:...} </script>
从上面的例子中,test对象从Hi.prototype,Year.prototype中继承下来;因此他能访问Year的原型方法method,同时他也能访问实例属性value
__proto__属性
__proto__属性(IE浏览器不支持)是实例指向原型对象的一个指针变量,他的作用就是指向构造函数的原型属性constructor,通过这两个属性,就可以访问原型里的属性和方法了。
javascript中的对象实例本质上是有一系列的属性组成的,在这些属性中,有一个内部不可见的特殊属性--__proto__,该属性的值指向该对象的实例的原型,一个对象实例只拥有一个唯一的原型。
<script type="text/javascript"> function Box() { //大写 构造方法 Box.prototype.name = "gudoumaoning"; //原型属性 Box.prototype.age = 21; Box.prototype.run = function() { //原型方法 return this.name + this.age + "studying"; } } var box1 = new Box(); var box2 = new Box(); console.log(box1.constructor); //构造属性,可以获取构造函数本身 //作用是被原型指针变量定位,然后得到构造函数本身 </script>
__proto__属性和prototype属性的区别
prototype是function对象中专有的属性。
__proto__是普通对象的隐世属性,在new的时候,会指向prototype所指的对象
__proto__实际上是某个实体对象的属性,而prototype则是构造函数的属性。
__proto__只能在学习或调试环境下使用。
原型模式的执行流程
1.先查找构造函数实例里的属性或方法,如果有,就立即返回。
2.如果构造函数的实例没有,就去他的原型对象里找,如果有就立即返回。
工厂模式
function createObject(name,age) { var obj = new Object(); obj.name = name; obj.age = age; return obj; }
工厂模式解决了实例化对象大量重复的问题,但还有一个问题,那就是根本无法搞清楚他们到底是哪个对象的实例。使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题。
使用构造函数的方法和工厂模式的不同之处在于:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this对象
3.没有return语句
当使用了构造函数,并且new 构造函数(),那么就在后台执行了new Object();
函数体内的this代表了new Object() 出来的对象
1.判断属性是在构造函数的实例里,还是原型里,可以使用hasOwnProperty()函数
2.字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反
3.如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的
4.如果是原型方法,那么他们的地址是共享的