前面介绍通过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方法,但是这两个方法是不同的函数实例。  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方法获取实例的原型对象。  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方法,返回字符串组成的数组。  var keys = Object.keys(Person.prototype); 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()方法。

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方法。

浅谈JavaScript的面向对象程序设计(二)的更多相关文章

  1. 浅谈JavaScript的面向对象程序设计(四)

    本文继续讲解JavaScript的面向对象程序设计.继承是面向对象语言中的一个基本概念,面向对象语言支持两种继承实现方式:接口继承和实现继承.接口继承只继承方法签名,而实现继承则继承实际的方法.但是在 ...

  2. 浅谈JavaScript的面向对象程序设计(三)

    前面已经对JavaScript的面向对象程序设计作了简单的介绍,包括了对象的属性.对象的工厂模式.构造函数和原型等.通过介绍,这些创建对象的方法依然有不少优化和改进的地方. 组合使用构造函数模式和原型 ...

  3. 浅谈JavaScript的面向对象程序设计(一)

    面向对象的语言有一个标志,他们都有类的概念,通过类可以创建多个具有相同属性和方法的对象.但是JavaScript中没有类的概念,因此JavaScript与其他的面向对象语言还是有一定区别的.JavaS ...

  4. 浅谈JavaScript的面向对象和它的封装、继承、多态

    写在前面 既然是浅谈,就不会从原理上深度分析,只是帮助我们更好地理解... 面向对象与面向过程 面向对象和面向过程是两种不同的编程思想,刚开始接触编程的时候,我们大都是从面向过程起步的,毕竟像我一样, ...

  5. 浅谈javascript的面向对象思想

    面向对象的三大基本特性 封装(把相关的信息(无论数据或方法)存储在对象中的能力) 继承(由另一个类(或多个类)得来类的属性和方法的能力) 多态(一个对象在不同情况下的多种形态) 定义类或对象 第一种: ...

  6. [转载]浅谈JavaScript函数重载

     原文地址:浅谈JavaScript函数重载 作者:ChessZhang 上个星期四下午,接到了网易的视频面试(前端实习生第二轮技术面试).面了一个多小时,自我感觉面试得很糟糕的,因为问到的很多问题都 ...

  7. 浅谈JavaScript浮点数及其运算

    原文:浅谈JavaScript浮点数及其运算     JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ...

  8. 浅谈javascript函数节流

    浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...

  9. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

随机推荐

  1. 1.5 sleep()方法

    方法sleep()的作用是在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行).这个"正在执行的线程"是指this.currentThread()返回的线程 ...

  2. BigDecimal工具类处理精度计算

    /** * Created by My_coder on 2017-07-27. * 加减乘除计算工具类 */ public class BigDecimalUtil { private BigDec ...

  3. tyvj4869 罪犯分组

    看数据范围,果断状压.真是比愤怒的小鸟还智障..... #include<bits/stdc++.h> using namespace std; #define MAXN 500000+1 ...

  4. 17. ZooKeeper常见的分布式系统任务——屏障

    以后几节中主要介绍以下内容: 如何执行领导者选举,组员管理和两阶段提交协议等常见的分布式系统任务 如何实现一些分布式数据结构,如屏障(barrier),锁(lock)和队列(queue) 这一章中概述 ...

  5. myeclipse将java项目转换成web项目,导出war包

    1.项目右击属性,勾选以下两项,点击apply,关掉窗体,又一次打开 2.打开assembly,删掉webroot文件夹.把相应的webapp文件夹加入进来 watermark/2/text/aHR0 ...

  6. JSP具体篇——response对象

    response对象 response对象用于响应client请求,向客户输出信息. 他封装了JSP产生的响应,并发送到client以响应client请求. 1.重定向网页 使用response对象的 ...

  7. Springboot的默认定时任务——Scheduled注解

    本博客参考博文地址. 1.pom依赖: 引入springboot starter包即可 <dependencies> <dependency> <groupId>o ...

  8. Shell编程实践之批量安装JDK

    实验环境 只有两台机子,一台虚拟机192.168.1.200,另外一台物理机192.168.1.201. 目标 执行一个shell脚本,给这两台机子自动安装jdk. 实验步骤 1.自动设置ssh免密码 ...

  9. 原生JS的Ajax

    转自:https://www.cnblogs.com/yufann/p/JS-Ajax.html 1.创建一个Ajax对象 非IE6浏览器:var obj = new XMLHttpReuqest() ...

  10. UML2和建模工具学习总结

    软件发展的方向:CS–>BS–>SOA–>BIG DATA 建模的含义: 模型是对现实的简化 从特点视角对系统的一个完整性描述 建模的重要性: 为了更好的理解一个系统 管理复杂度(也 ...