JS面向对象整理

有次面试的时候,面试官让我谈谈对面向对象的理解,让我一下蒙了,竟然不知道从哪里说起?自己都是在看完视频后,直接用面向对象写东西,也没有好好梳理,导致自己只会简单的用一下,却不会说。于是我就翻了翻《JavaScript高级程序设计》,对其进行整理了一下。

1.什么是对象

在ECMAScript中,对象就是一堆无序属性的集合,这些属性可以是基本值,也可以是别的对象和函数。所以我们也可以吧对象当成一组名值对,一个属性名对应一个值,值可以是数据或方法。

2.创建一个对象

(1)创建一个最简单的对象是通过创建一个Object的实例,再为它添加属性和方法:

var person = new Object();
person.name = "Tom";
person.age = 18;
person.sayName = function(){
alert(this.name);
};

(2)使用Object创建对象的缺点很明显,就是它只能创建一个对象,所以我们开始使用工厂模式来创建对象

            function createPerson(name,age,job){
                var o = new Object();
                o.name = name;
                o.job = job;
                o.age = age;
                o.sayname = function(){
                    alert("你好" + name);
                }
                return o;
            }
            var person1 = createPerson("tom",16,"doctor");
            person1.sayname();
            var person2 = createPerson("Candy",18,"police");

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题,即怎样知道一个对象的类型。

(3)构造函数模式

构造函数模式就可以创建特定类型的对象,我们将上一个函数改成构造函数如下:

            function Person(name,age,job){
                this.name = name;
                this.age = age;
                this.job = job;
                this.sayname = function(){           alert("My name is" + this.name);          };
            }var person1 = new Person("tom",16,"doctor");
            var person2 = new Person("Candy",18,"police");
            alert(person1 instanceof Object);
            alert(person1 instanceof Person);

上面的函数与工厂模式的主要不同在于它直接将属性复制给this对象,并没有返回值,并且构造函数名都应是首字母大写,以便与其他函数做区别,因为构造函数本身也是函数,只不过可以用来创建对象而已 。

然后是创建Person的实例,这就用到了new操作符,我们就是用new创建了2个实例。在这两种实例中,都包含有一个constructor属性,这个属性指向的就是Person,就是实例的构造函数,例如:

alert(person1.constructor == Person);

这里弹出的就是true,person2同样是这样。而我们最后的instanceof操作符就是为了检测对象类型。上面的两条检测语句弹出的都是true,说明我们创建的实例,既是Person的实例,有属于Object的实例。

但构造函数也有一个缺点,就是相同的属性或方法会创建多遍,这样确实有些不必要。

(4)原型模式

首先,我们要知道每一个函数都有一个prototype的原型属性,这个属性是指向一个对象,这个对象可以包括属性和方法用来给实例共享,也可以说prototype就是通过调用构造函数而创建那个对象实例的原型对象。所以称prototype为原型属性是针对于构造函数的,原型对象是针对创建的实例的

            function Person(){

            }
            Person.prototype.name = "Nancy";
            Person.prototype.age = 16;
            Person.prototype.sayName = function(){
                alert(this.name);
            }
            var person1 = new Person();
            var person2 = new Person();

这个原型模式有一个最大的问题,不是数据重复,而是在对于引用类型的属性,例如:

function Person(){
}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true

我们发现我们修改了person1的friends数组,但person2引用的friends也发生改变

(5)构造函数与原型函数相结合使用

function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["Shelby", "Court"];
}
Person.prototype = {
  constructor : Person,
  sayName : function(){
    alert(this.name);
  }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

这种构造函数与原型混成的模式,是目前在 ECMAScript 中使用最广泛、认同度最高的一种创建自定义类型的方法。

(6)其它的模式

在《JavaScript高级程序设计》众还有其它的设计模式,如动态原型模式,寄生构造函数模式,稳妥构造函数模式等,但我们平时掌握上一种方法就够用了,所以我就偷个懒,就不介绍这几种了。

1.继承

许多 OO 语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在 ECMAScript 中无法实现接口继承。 ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。

(1)原型链

讲到继承就不得不讲一讲原型链了,我们先讲一个对象的原型链:

function Person(name,age){
      this.name = name;
      this.age = age;
}
Person.prototype.sayName = function(){
      alert(this.name);
}

var person1 = new Person("tom",18);

在上面的例子中,我们创建了一个Person构造函数,这个构造函数有一个原型对象prototype,并且这个原型对象有一个属性constructor,这个属性是指向它的构造函数,即Person,在创建的实例中,person1也有一个原型属性_proto_,这个属性指向prototype,即Person.prototype。总的来说就是:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。这就是圆形脸的基本概念。

利用原型链实现继承可以这么写:

            function SuperType(){
                this.property = true;
            }
            SuperType.prototype.getSuperValue = function(){
                return this.property;
            };

            function SubType(){
                this.SubProperty = false;
            }
            //继承了SuperType
            SubType.prototype = new SuperType();

            SubType.prototype.getSubValue = function(){
                return this.SubProperty;
            };

            var instance = new SubType();
            alert(instance.getSuperValue()); //true

利用原型链虽然好用,但缺点也很大,其中一个就是方才讲到的对引用类型属性的改变:

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

原型链的第二个问题是:在创建子类型的实例时,不能向父类型的构造函数中传递参数。

(2)借用构造函数

这种技术的基本思想相当简单,即在子类型构造函数的内部调用父类型构造函数。因此通过使用 apply()和 call()方法也可以在(将来)新创建的对象上执行构造函数。

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    //继承了 SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"

通过使用 call()方法(或 apply()方法也可以),我们实际上是在(未来将要)新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的所有对象初始化代码。

但是,根据我们的套路,如果只是用构造函数的来实现继承,还是有问题的,那就是方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在父类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。所以我们和创建对象一样,采取两者相结合的方法。

(3)组合继承

            function SuperType(name){
                this.name = name;
                this.colors = ["red","black"];
            }
            SuperType.prototype.sayName = function(){
                alert(this.name);
            }
            function SubType(name,age){
                //继承属性
                SuperType.call(this,name);
                this.age = age;
            }
            //继承方法
            SubType.prototype = new SuperType();
            SubType.prototype.constructor = SubType;
            SubType.prototype.sayAge = function(){
                alert(this.age);
            }

            var insstance = new SubType("tom",16);
            insstance.sayName();
            insstance.sayAge();

            insstance.colors.push("yellow");
            console.log(insstance.colors);

            var insstance2 = new SubType("Nancy",100);
            console.log(insstance2.colors);
            insstance2.sayName();
            insstance2.sayAge();

在这个例子中, SuperType 构造函数定义了两个属性: name 和 colors。 SuperType 的原型定义了一个方法 sayName()。 SubType 构造函数在调用 SuperType 构造函数时传入name 参数,紧接着又定义了它自己的属性 age。然后,将 SuperType 的实例赋值给 SubType 的原型,然后又在该新原型上定义了方法 sayAge()。这样一来,就可以让两个不同的 SubType 实例既分别拥有自己属性——包括 colors 属性,又可以使用相同的方法了。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。但它也有缺点,就是它在继承的时候会调用两次父类型构造函数 。

(4)这本书还有其他的继承方法,但是对于我这样写一写小项目的来说,上一种就够了,其他的我也只是做一种了解,我就讲一个寄生组合式的继承方式,

我们先写一个利用原型继承的函数

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

我们在 object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例

//寄生组合式继承
function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象赋值给子类型的原型。

利用这个寄生组合的模式可以解决上面组合继承的不足,被开发人员认为是引用类型最理想的继承范式 。

时间: 2024-10-08 10:28:50

JS面向对象整理的相关文章

[js笔记整理]面向对象篇

一.js面向对象基本概念 对象:内部封装.对外预留接口,一种通用的思想,面向对象分析: 1.特点 (1)抽象 (2)封装 (3)继承:多态继承.多重继承 2.对象组成 (1)属性: 任何对象都可以添加属性,(如右侧示例可弹出arr.a为12 var arr=[1,2,3];arr.a=12;alert(arr.a) 全局变量是window的属性, 1 <script> 2 window.a=12; 3 window.onload=function() 4 { 5 alert(a); 6 } 7

js面向对象初步探究(上) js面向对象的5种写方法

很长一段时间看网上大神的JS代码特别吃力,那种面向对象的写法方式让人看得云里来雾里去.于是就研究了一下JS面向对象,由于是初学,就将自己在网上找到的资料整理一下,作为记忆. js面向对象的5种写方法:(来自http://www.iteye.com/topic/434462) 首先 定义circle类,拥有成员变量r,常量PI和计算面积的成员函数area(): //第1种写法 function Circle(r) { this.r = r; } Circle.PI = 3.14159; Circl

javascript 面向对象整理

整理一下js面向对象中的封装和继承. 1.封装 js中封装有很多种实现方式,这里列出常用的几种. 1.1 原始模式生成对象 直接将我们的成员写入对象中,用函数返回. 缺点:很难看出是一个模式出来的实例. 代码: function Stu(name, score) { return { name: name, score: score } } var stu1 = Stu("张三", 80); var stu2 = Stu("李四", 90); console.log

js面向对象学习 - 对象概念及创建对象

原文地址:js面向对象学习笔记 一.对象概念 对象是什么?对象是“无序属性的集合,其属性可以包括基本值,对象或者函数”.也就是一组名值对的无序集合. 对象的特性(不可直接访问),也就是属性包含两种,数据属性和访问器属性. 1.数据属性又包含 Configurable //表示能否通过delete删除,默认为true: Enumerable //表示能否通过for-in循环返回属性,默认为true; Writable  //表示能否修改属性的值,默认为true; Value            

js面向对象编程:数据的缓存

js也可以通过缓存数据,来加快处理速度.在必要的时候使用空间还换取时间还是值得的.例如需要很长时间才能完成的计算,就可以把计算结果缓存到客户端,以后就可以直接使用缓存的计算结果,不在重复计算. 1简单函数的计算结果的缓存 2递归函数的计算结果的缓存  3Ajax读取数据的缓存 1简单函数的计算结果的缓存 例如: //共用函数,封装内部调用,缓存计算结果 function memorize(f) { var cache={}; return function(){ var key=argument

js面向对象的系列

在面向对象语言中如java,C#如何定义一个对象,通常是定义一个类,然后在类中定义属性,然后通过new 关键字来实例化这个类,我们知道面向对象有三个特点,继承.多态和封装.那么问题来了,在javaScript中如何定义一个类?在javaScript中如何定义类的属性?如何继承?带着这些问题开始我们的js面向对象之旅吧. 在js中如何定义类? js中是没有类的概念的,但是我们通常会用一个函数定义成一个类.funtion class1(){ //类的成员定义 } 这里class1既是一个函数也是一个

简单粗暴地理解js原型链--js面向对象编程

简单粗暴地理解js原型链--js面向对象编程 原型链理解起来有点绕了,网上资料也是很多,每次晚上睡不着的时候总喜欢在网上找点原型链和闭包的文章看,效果极好. 不要纠结于那一堆术语了,那除了让你脑筋拧成麻花,真的不能帮你什么.简单粗暴点看原型链吧,想点与代码无关的事,比如人.妖以及人妖. 1)人是人他妈生的,妖是妖他妈生的.人和妖都是对象实例,而人他妈和妖他妈就是原型.原型也是对象,叫原型对象. 2)人他妈和人他爸啪啪啪能生出一堆人宝宝.妖他妈和妖他爸啪啪啪能生出一堆妖宝宝,啪啪啪就是构造函数,俗

js面向对象程序设置——创建对象

<script type="text/javascript">            //工厂方式        //1.原始方式        /* var objCar=new Object();        objCar.name="劳斯莱斯";        objCar.color="blue";        objCar.showColor = function() {          alert(this.colo

js面向对象编程:如何实现方法重载

js中如何实现方法重载?这涉及到三个问题 1同名函数的调用问题 2函数中特殊的参数arguments 3如何利用arguments实现方法重载 1同名函数的调用问题 都知道在js中如果存在多个名称相同的函数,则调用实际每次都只使用最后一个,js其实是没有重载的,也就是说,如果定义了多个同名的函数,单参数不一样,在调用时,js不管参数个数,只管前后顺序 例如: function test1(arg1) { alert("参数1:"+arg1); } function test1(arg1