前面介绍通过 Object 构造函数或者字面量创建单个对象,但是通过这个的方法创建对象有明显的缺点:调用同一个接口创建多个实例,会产生大量的重复代码。怎么样解决?
工厂模式工厂模式是软件工程领域经常使用的一种设计模式,这种设计模式抽象了创建对象的具体过程。由于在 JavaScript 中无法使用类,可以使用函数来封装特定接口创建对象。
- function createPerson(name, age, sex) {
- var obj = new Object();
- obj.name = name;
- obj.age = age;
- obj.sex = sex;
- obj.getName = function() {
- return this.name;
- }
- }
- var person1 = createPerson("ge", "18", "man");
- var person2 = createPerson("james", "18", "man");
函数 createPerson 能够接受参数创建 person 对象。可以多次调用这个函数,创建拥有同样属性和方法的对象。工厂模式解决了多次创建,代码重复的问题,但是却遇到了对象识别的问题。我们没有办法识别这些对象。
构造函数模式前面介绍过构造函数可以创建特定类型的对象。对于 Object 和 Array 这样的构造函数,在程序运行时,会自动执行在环境中。通过自定义的函数创建属性,可以自己定义属性和方法。
- var obj = {};
- function Person(name, age, sex) {
- this.name = name;
- this.age = age;
- this.sex = sex;
- this.getName = function() {
- return this.name;
- }
- }
- var person1 = new Person("ge", 18, "man");
- var person2 = new Person("ha", 19, "man");
- console.log(obj instanceof Person); //false
- console.log(person1 instanceof Person); //true
- console.log(person2 instanceof Person); //true
上面的代码,通过字面量创建了一个对象 obj。定义一个函数 Person,通过 this 添加了三个属性和一个方法。10 行和 11 行创建了两个 person 对象,通过 instanceof 来判断对象的类型。通过上面的代码创建对象,没有调用 Object 方法,直接将属性和方法赋值给 this。
创建 Person 对象实例,要经历以下过程:创建一个新对象;将构造函数的作用域赋值给新对象;执行构造函数中的代码;返回新对象。
在前面的代码中,person1 和 person2 分别保存这 Person 对象的不同实例。这两个实例有一个实例属性 constructor 属性,该属性指向 Person
- console.log(person1.constructor==Person);//true
- console.log(person2.constructor==Person);//true
构造函数与其他函数的唯一区别,就是调用他们的方式不同。任何函数只要通过 new 操作符来调用,那它就可以作为构造函数;如果不通过 new 来调用,那它就是普通函数。
- var person1 = new Person("ge", 18, "man");
- Person("jiang", 18, "man");
- window.getName();
上面的代码显示了函数的普通调用以及 new 操作符来调用。如果不通过 new 操作符调用函数,则属性和方法都会被添加到 window 中。构造函数创建对象的主要缺点是每个方法在创建对象的时候,是重新创建了一遍。person1 和 person2 都有 getName 方法,但是这两个方法是不同的函数实例。 1 console.log(person1.getName==person2.getName);//false ,这句代码可以说明 getName 是不同的函数实例。
- function Person(name, age, sex) {
- this.name = name;
- this.age = age;
- this.sex = sex;
- this.getName = getName;
- }
- function getName() {
- return this.name;
- }
上面的代码在 Person 外部定义的函数 getName,在内部通过 this 指向了外部的方法。但是 getName 是全局变量,如果全局变量只能给一个函数使用,那这个全局变量实在没有存在的必要。
原型模式我们创建的每一个函数都有 prototype 属性,他是一个指针,指向一个对象。这个对象的用途是可以由特定类型的所有实例共享属性和方法。prototype 就是通过调用构造函数创建的实例的原型对象。使用原型的优势是可以让所有的对象实例共享属性和方法。不必的在构造函数中定义对象的属性和方法,可以通过原型来创建。
- function Person(name, age, sex) {
- this.name = name;
- this.age = age;
- this.sex = sex;
- }
- Person.prototype.getName = function() {
- return this.name;
- }
- var person1 = new Person("haha", "ge", "man");
- var person2 = new Person("hehe", "dd", "man");
- console.log(person1.getName == person2.getName); //true
上面的代码定义了一个对象 Person,通过 this 方法给对象添加了属性,通过原型给对象添加了方法。并在第九行和第十行新建了对象的两个实例。通过测试,这个两个实例的 getName 相等,也就是说这两个实例的 getName 方法指向同一个函数。
无论什么时候,只要创建了一个函数,JavaScript 就会根据特定的规则为函数创建 prototype 属性,这个属性指向函数的原型对象。默认情况下,这个属性会自动获得一个 constructor 属性,constructor 指向函数指针。Person.prototype.constructor 指向 Person 函数。创建了自定义的函数后,原型对象默认只有一个属性 constructor 属性;其他属性则是从 Object 对象继续而来。当调用构造函数创建一个实例后,该实例的内部将包含一个指针,指向构造函数的原型对象。 console.log(Person.prototype.isPrototypeOf(person1)); 通过 isPrototypeOf 可以确定 person1 是否是 Person.prototype 原型对象。 console.log(Object.getPrototypeOf(person1)==Person.prototype); 上面的代码通过 Object 的 getPrototypeOf 方法获取实例的原型对象。 1 console.log(Object.getPrototypeOf(person1).getName); 通过该方法获取的是实例的原型对象,并能够获取原型对象拥有的属性和方法。
通过对象实例能够访问保存在原型中的属性值,但是却不能通过对象实例修改原型中的属性值,如果给对象实例添加了一个和原型实例相同名字的属性,则原型中的属性将会被覆盖。
- function Person() {
- }
- Person.prototype.Name = "haha";
- Person.prototype.age = "19";
- var person1 = new Person();
- var person2 = new Person();
- person1.Name = "jordan";
- console.log(person1.Name); //jordan
- console.log(person2.Name); //haha
上面的代码,我们定义了一个函数,并为函数通过原型的方法添加了属性 name 和 age。然后通过 new 构造函数创建了该对象的两个实例 person1 和 person2。为 person1 添加了属性 name,其实是覆盖了原型中的属性 name,通过下面的输出可以确定,person2 的输出依然是原型中属性的值。
当为对象实例添加一个属性时候,如果该属性与原型中的属性重名,则会屏蔽原型中的属性。也就是说,添加这个属性会阻止我们访问原型中的属性,并不会修改原型中属性的值。对象的属性检索是从对象本身实例开始,然后再到原型中查找,如果查找到,则不会继续检索。delete 操作符可以删除实例中的属性,从而可以继续访问原型属性。
- function Person() {
- }
- Person.prototype.Name = "haha";
- Person.prototype.age = "19";
- var person1 = new Person();
- var person2 = new Person();
- person1.Name = "jordan";
- console.log(person1.Name); //jordan
- delete person1.Name;
- console.log(person1.Name); //haha
上面的代码,我们使用 delete 操作符删除了 person1 实例中的属性 Name,但是我们通过 person1Name 访问到的就是原型对象的属性。可以通过 hasOwnProperty 方法来判断属性是在实例中,还是在对象中。该方法继承于 Object 中。
- function Person() {
- this.sex = "man";
- }
- Person.prototype.Name = "haha";
- Person.prototype.age = "19";
- var person1 = new Person();
- var person2 = new Person();
- person1.Name = "jordan";
- console.log(person1.hasOwnProperty("Name")); //true
- console.log(person1.Name); //jordan
- delete person1.Name;
- console.log(person1.hasOwnProperty("Name")); //false
- console.log(person1.Name); //haha
上面的代码通过 hasOwnProperty 方法判断了 name 属性是属于原型还是属于实例。
- function Person() {
- }
- Person.prototype.Name = "haha";
- Person.prototype.age = "19";
- var person1 = new Person();
- for (key in person1) {
- console.log(key);
- }
通过 for-in 循环能够获取到实例的原型属性以及方法。当然也可以通过 in 来判断实例是否拥有属性。 console.log("Name" in person1); 通过 in 操作符合方法 hasOwnProperty 能够确认属性是存在于对象中还是原型中。in 操作符无论是对象中属性还是原型中属性都返回 true,hasOwnProperty 只有存在于实例中才返回 true。
- //true,实例中属性
- //false,原型中属性
- function hasOwnProperty(obj, name) {
- return obj.hasOwnProperty(name) && (name in obj);
- }
要获取对象上所有可以枚举的属性,可以通过 Object.keys 方法,返回字符串组成的数组。 1 var keys = Object.keys(Person.prototype); 2 console.log(keys.toString());//Name,age 。Object.keys 方法获取了 Person 中可以枚举的属性。
- var allkeys=Object.getOwnPropertyNames(Person.prototype);
- console.log(allkeys.toString());//constructor,Name,age
通过 Object.getOwnPropertyNames 能够获取所有的属性,包括不可枚举的属性。
在原型中的修改是一次搜索,我们对原型的修改,会立即从实例上反映出来。即使是先创建实例,再修改原型,也可以。
- function Person(){
- }
- var person1 = new Person();
- Person.prototype.getName=function(){
- return "hehe";
- }
- console.log(person1.getName());//hehe
上面的代码定义了函数,并为对象创建了实例。在实例化之后,我们在原型中添加了属性 getName,但是我们依然能够访问该原型。当我们在 person1 中调用 getName 时,首先在实例中搜索,如果搜索不到,则到原型中进行检索。原型与实例之间的连接是一个指针,因此可以在原型中找到 getName 属性。但是如果我们重写了原型对象,则无法访问到属性。实例中的指针仅仅指向原型,但是无法指向构造函数。
- function Person(){
- }
- var person1 = new Person();
- Person.prototype={
- getName:function(){return "hehe"}
- };
- var person2 = new Person();
- console.log(person2.getName());//hehe
- console.log(person1.getName());//报错
上面的代码就显示了重写原型对象后,person1 已经无法调用到原型的属性和方法了。但是 peprson2 可以调用到。
JavaScript 中的引用类型都采用了原型模式。原生引用类型(Array、String、和 Object)都在构造函数的原型上定义了方法。比如在 String 的原型用有 toString() 方法。
1 console.log(typeof String.prototype.toString);//function
。同时我们还可以为原生引用类型添加新的方法。比如我们为 String 添加 getString 方法。
- String.prototype.getString=function(){
- return this.toString().trim();
- }
- var str=" wo ";
- console.log(str.getString());//wo
上面的代码,我们为 String 的原型添加了 getString 方法,该方法调用了 toString 方法和 trim 方法。
来源: https://www.cnblogs.com/ggz19/p/8205743.html