*为更加清晰的了解原生JavaScript中的创建对象&函数,本文均采用ES5的语法。
一、最简单的创建对象
1.使用new关键字创建Object
var student = new Object;
student.id = 12345;
student.name = "xiaoming";
student.sex = "male";
student.word = function() {
alert("I am Xiaoming!")
}
2.对象字面量
var student = {
id: 12345,
name: "xiaoming",
sex: "male",
word: function() {
alert("I am Xiaoming!")
}
}
上面的两种方式,会产生大量的代码,而且没有复用性。由此,我们可以考虑采用工厂模式,即类似于工厂用一条流水线,批量生产的方式创建对象:
二、工厂模式
function createStudent(name,sex,id) {
var o = new Object();
o.name = name;
o.sex = sex;
o.id = id;
o.word = function(){
alert(this.name);
}
return o;
}
上面定义的函数createStudent,就是使用工厂模式的方式创建对象的。每次调用函数时,新建一个对象并给其属性赋值。
var student1 = createStudent("xiaoming", "male", 12345);
var student2 = createStudent("xiaoling", "female", 54321)
和直接创建对象相比,工厂模式大大节省了代码量,并且可复用,解决了重复实例化多个对象的问题。但同时也有一定的弊端:没有解决对象识别的问题。在JavaScript中,对象还包括Date、Array等特殊对象,仅通过工厂模式无法解决对象类型的识别。由此,我们可以采用构造函数:
三、构造函数模式
function Student(name,sex,hobby) {
this.name = name;
this.sex = sex;
this.hobby = hobby;
this.word = function(){
alert(this.name);
}
}
以此方法调用构造函数步骤 {
1、创建一个新对象
2、将构造函数的作用域赋给新对象(将this指向这个新对象)
3、执行构造函数代码(为这个新对象添加属性)
4、返回新对象
}
//调用并生成对象
var student1 = new Student ("xiaoming", "male", ["singing", "soccer", "game"]);
var student2 = new Student ("xiaoling", "female", ["comic", "dancing", "reading"]);
console.log(student1 instanceof Object); //true
console.log(student1 instanceof Student); //true
console.log(student2 instanceof Object); //true
console.log(student2 instanceof Student ); //true
console.log(student1.constructor); //constructor 属性返回对创建此对象的数组、函数的引用
instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。由上例可以看出,通过构造函数创建的student1、student2既是Student的实例,也是Object的实例。这说明通过构造函数创建的对象是可以判断其对象类型的。
构造函数对比工厂模式有以下不同之处:
1、没有显式地创建对象
2、直接将属性和方法赋给了 this 对象
3、没有 return 语句
构造函数依然存在一些问题。虽然不再重复的显式创建对象,但是每次new一个Student时,其实也是每次都创建了一个Function。而Function也是对象的一种类型。其本质是多次创建了函数。为解决这个问题,产生了原型模式。
四、原型模式
function Student() {
}
Student.prototype.name = "xiaoming";
Student.prototype.sex = "male";
Student.prototype.hobby= ["singing", "soccer", "game"];
Student.prototype.word = function(){
alert(this.name);
};
由上面代码可以看出,Student的属性定义在了它的原型(prototype)上。这样的方式可以使得Student所有对象实例共享它的属性和方法,无需在每次创建对象时都要创建一份新的属性和方法,直接定义属性和方法在对象的原型上,可以使得所有实例都直接继承。
console.log(Student.prototype); //Object{name: 'xiaoming', sex: "male", hobby: Array[3]}
var student1 = new Person(); //创建一个实例person1
console.log(student1.name); //xiaoming
student1.name = "xiaojun";
console.log(student1.name); // xiaojun
上文可以看出,在实例上再次定义同名的属性(私有属性),可以覆盖其原型上的公有属性。
五、组合模式(构造函数模式+原型模式)
单纯的构造函数和原型模式各有优缺点。因此,我们可以利用构造函数模式定义实例属性,原型模式用于定义方法和共享的属性,结合两者的优点使用。
//构造函数定义属性:
function Student(name,sex,hobby){
this.name = name;
this.sex= sex;
this.hobby= hobby;
}
//原型定义方法:
Student.prototype = {
constructor: Student, //让构造器指向自身
word: function(){
alert(this.name);
}
}
var student1 = new Student("xiaoming","male",["singing", "soccer", "game"]);
console.log(student1);
上面代码中的constructor的意思是,每个函数都有prototype属性,指向该函数原型对象,原型对象都有constructor属性,是一个指向prototype属性所在函数的指针。具体对面向对象的constructor的解读,可看文章连接:https://www.jianshu.com/p/49e5e848fee0
在其他面向对象的语言中,属性和方法往往是封装在一起的,而在组合模式中,构造函数和原型是分开定义了属性和方法的,所以并没有真正意义上的封装,因此,动态原型模式正是解决这一问题的方案。
六、动态原型模式
动态原型模式是将属性和方法都封装在构造函数中(包括原型属性和实例属性),通过在构造函数中实例化原型(仅在必要的情况下)实现封装,又保持了同时使用构造函数和原型的优点。其本质是在构造函数中加了一层对于方法的判断,从而实现动态生成原型上的方法。
function Student(name,sex,hobby){
this.name = name;
this.sex = sex;
this.hobby = hobby;
if(typeof this.word != "function") {//这段判断语句的作用是限制Person.prototype属性(原型属性对象)只生成一次,要不然每次实例化一个Person对象都会去写一遍原型对象
Student.prototype.word = function () {
alert(this.name);
}
}
}
var student1 = new Student("xiaoming","male",["singing", "soccer", "game"]);
创建Student构造函数时,会创建一个prototype属性,该属性实际上就是Student.prototype的原型对象,prototype属性是一个指针,指向Person.prototype的原型对象,
当this指向的属性非方法(typeof this.word != "function"),则在当前对象的原型上创建一个新方法。避免了重复修改原型方法。
网友评论