创建对象
Js中可以用构造函数模式创建对象,如:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); } } var person1 = new Person("Nicholas", 29, "aa"); var person2 = new Person("YU", 29, "BB");
这里应注意函数名首字母应大写,按照约定,构造函数始终都应该以一个大写字母开头,而非构造函数则应以一个小写字母开头。
原型对象
上述方法中,有一个缺点,就是对象中的sayName方法,每次都会重新new一个对象,因此,也就是说不同对象的sayName方法地址是不同的,然而这个方法都只是执行提示姓名,这种创建两个相同的function,完成同样的任务,确实没有必要。所以这存在了缺陷。使用下面的方法可以避免这种缺陷。
function Person() { } Person.prototype.name = "YuKaifei"; Person.prototype.age = 25; Person.prototype.job = "SoftWare"; Person.prototype.sayName = function () { alert(this.name); } var person1 = new Person(); person1.sayName();//YuKaifei var person2 = new Person(); person2.sayName(); //YuKaifei alert(person1.sayName == person2.sayName); //true
理解原型对象
函数
Person |
|
prototype |
指向所对应的原型对象 |
函数的原型对象
Person Prototype (person的原型对象) |
|
constructor |
指向所对应函数(person)的指针 |
Name |
“YuKaiFei” |
Age |
25 |
Job |
“Soft Ware” |
sayName |
(function) |
自定义函数1
Person1 |
|
prototype |
指向所对应的原型对象 |
自定义函数2
Person2 |
|
prototype |
指向所对应的原型对象 |
无论什么时候,只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性就指向所对应的原型对象。而默认情况下,原型对象的constructor属性会指向所对应函数的地址。也就是说这时这两个对象各有一个属性,是存放对方的地址的。
当调用一个构造函数创建一个新实例之后,该函数内部也有一个属性prototype,这个属性是执行原型对象的地址。也就是说新实例其实与构造函数并没有直接关系。
需要注意的是,新实例虽然没有属性和方法,但是却可以通过查找对象属性的方式来调用原型对象中的属性和方法。
当新的实例创建新的属性之后,如果和原型对象是相同的属性,那么在下次调用时会调用新实例的属性,而不是原型对象的属性。如:
var person1 = new Person(); person1.name="aaaa"; var person2 = new Person(); alert(person1.name);//aaaa alert(person2.name);//YuKaifei
in 操作符
有两种方式可以使用in,一种是单独in,一种是for-in,需要注意的是,无论该属性是存在于实例中还是存在于原型中,只要存在,就返回true。
例如 alert(name in person1) 返回true
for (var prop in person1) { if (prop == "name") { alert("name") } }
判断该属性是否存在实例中的方式是hasOwnPropery()方法。
例如:person1.hasOwnProperty(“Name”) 返回true
Person2.hasOwnProperty(“Name”) 返回false
获取对象上所有可枚举的实例属性:
var keys = Object.keys(Person.prototype); alert(keys); //name,age,job,syName,注意keys是一个数组。
更简单的原型语法—存在缺陷
Person.prototype = { name: "Nicholas", age: 29, jon: "SowfWare", syaName: function () { alert(this.name); } }
可以使用这种方法更简单的创建原型,但需要注意的是,这种写法相当于重写了原型对象,所有consturctor属性是新的,即不在指向person。如:
function Person() { } var friend = new Person(); Person.prototype = { name: "Nicholas", age: 29, jon: "SowfWare", syaName: function () { alert(this.name); } } friend.sayName(); //error
上面代码会报错,原因在与重写了原型对象,指向丢失,也就是切断了现有原型与之前已经存在的对象之间的联系。如果避免这种方法可以在声明原型中指定: constructor:Person
原生对象的原型—String、Array
原型的模式体现在所有原生的引用类型,例如object、array、string等,都在其构造函数的原型上定义了方法,例如在Array.prototype中可以找到sort()方法,在string.prototype可以找到substring()方法。
通过原生对象的原型,不仅可以获得所有默认方法的引用,也可以随意的修改原生对象的原型,因此可以随时添加方法。例如:
为String添加一个名为startsWith() 方法。
String.prototype.startsWith = function (text) { return this.indexOf(text) == 0; } var msg = "Hello world!"; alert(msg.startsWith("Hello")); //true
优化原型对象的缺陷
原型对象为了省略函数传递初始化这一环节,结果所有的实例在默认情况下都会取得相同的值,这对于值类型属性共享还可以,但是对于引用类型则会存在问题。如:
function Person() { } Person.prototype = { name: "Nicholas", age: 29, jon: "SowfWare", friends:["Yu","Kai"], syaName: function () { alert(this.name); } } var person1 = new Person; person1.name = "yy"; var person2 = new Person; alert(person1.name); //yy alert(person2.name); //Nicholas person1.friends.push("Fei"); alert(person1.friends); //YuKaiFei alert(person2.friends); //YuKaiFei
可以看到值类型并没有什么影响,但因为引用类型的特殊,所有实例的值都将会被改变。
所有最好采用动态原型模式
动态原型模式-声明原型的推荐方式
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Yu", "kai"]; if (typeof this.sayName != "function") { this.sayName = function () { alert(this.name); }; } } var f1 = new Person("Y", 29, "s"); var f2 = new Person("Y", 29, "s"); f1.friends.push("Fei"); alert(f1.friends); //yu,kai,fei alert(f2.friends);//yu,kai