面向对象

作者: 小妖德八戒 | 来源:发表于2017-06-26 21:53 被阅读0次

    面向对象不是一门新技术,而是一门解决问题的新思路。

    面向对象是相对于面向过程的一种解决问题新思路,在知道面向对象之前亲我们需要先明白什么是面向过程。

    面向过程是解决某一件事情时,按照完成某件事所需要的步骤来一步一步解决问题过程,当每一步都完成时,整件事就已经完成了。

    面向对象则是站在对象的角度思考问题,我有哪些特征和行为。也可以说是让对象以我们人类认知世界和思考的方式来构造对象。例如:我们刚出生时,在认识一个新事物时,我们将以自身的五官和感知,将这个事物抽象成一个概念型模型并定义一个概念名称。当某些事物符合这个模型的固有特征时,我们就会将它称为这个概念名称。

    而这个概念名称我们称为类,而对象是类的实例化,类是对象的抽象化。万物皆对象,我们却将具有相同特征的对象称为一个类。

    面向对象的语言:1.封装 2.继承 3.多态 (4.抽象 但是并没有得到大众的认可);面向对象的语言也是一个类。只要一门语言满足这三个特征,这门语言就是面向对象的语言这一个类的实例化对象。

    j s得到对象的方法有三种:1. new Object; 2. var 变量 ={} 3. 通过构造函数得到。

    通过第一和第二种创建的对象,一旦对象较多,会造成代码的冗余。

    通过工厂模式得到的创建对象,虽然解决了代码冗余的问题。但是出现一个新的问题。我们的对象应该是有类的,而工厂模式得到的对象只是对象这一类。

    当工厂模式无法满足我们的需求时,j s给出了一个新的解决问题的方法就是构造函数。

    p s:判断类别的方法: instanceof 和typeof来判断。

    构造的函数首字母应该大写,这是j s 中的潜规则。

    构造函数的方法来创建对象:

    js在ES6之前是是没有办法来定义一个类的。所以我们使用构造函数来模拟类的概念。

    在说构造函数时,我们需要来知道我们在操作这些代码时计算机内存中发生了什么。

    我们在定义一个变量时,栈内存将我们定义的变量以二进制的方式存储了起来,而我们变量的内容将会放进堆内存中或数据区内存中。在变量内放着的只是指向我们定义内容的一个以16进制存储的地址。

    基于构造函数的创建对象的方式和基于工厂的方式类似

    最大的区别就是函数的名称就是类的名称,按照面向对象语句的

    function Person(name,age) {

    this.name = name;

    this.age = age;

    this.say = function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    }

    var p1 = new Person("马天鹏",15);

    p1.say();

    使用构造函数的好处就是可以使用instanceof来判断这个对象的类型了

    alert(p1 instanceof Person)

    基于构造函数的定义的方式最大的好处除了对象重复使用外,就是让我们还可以判断它

    的类型。

    此时我们发现基于构造函数的定义对象的方式看似已经很完美了,我们需要的问题它都

    可以解决了,但是如果我们仔细的分析这段代码的话,就会发现这样的代码是存在问题的,

    为什么呢?

    我们通过代码分析得知:say 方法在每个对象创建后都存在了一个方法拷贝(但是我们

    发现代码在只有调用时,say 方法才会在堆中创建,基于闭包的原理),这样就增加了内存的

    消耗了,如果在对象中有大量的方法时,内存的消耗就会高,这样不行了。

    解决这个问题的就是我们可以把这个方法放到全局函数,这样就所有的对象指向了一个

    方法。

    解决方案就是将方法全部放在外面,成为全局函数

    function Person(name,age) {

    this.name = name;

    this.age = age;

    可以将方法成为全局函数。

    this.say = say;

    }

    function say() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    var p1 = new Person("马天鹏",15);

    p1.say();

    var p2 = new Person("老万",30);

    p2.say();

    但是这样写的话,会带来另一个问题,就是方法一点定义为全局函数,那么 window 对

    象就可以调用,这样就破坏了对象的封装性。而且如果有大量的方法,这样写导致整体代码

    充斥着大量的全局函数,这样将不利于开发。所以我们急需一种可以完美的解决上述问题的

    方案,javascript 给我们提供了一种解决这些问题的方案,就是基于原型的对象创建方案。

    封装--Javascript 的原型(prototype)

    Prototype,原型的初览

    以上方式在创建对象都不太理想,所以我们可以使用 prototype 的方式来完成对象的创

    建。

    如何使用原型创建对象呢?首先写段代码让大家看看:

    //定义了一个对象

    function Person() {

    }

    //使用原型来给对象赋值

    //这样就讲一个对象的属性和方法放在了该对象的原型中

    //外界是无法访问到这样数据的

    Person.prototype.name = "小妖的八戒";

    Person.prototype.age = 18;

    Person.prototype.say = function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    var p1 = new Person();

    p1.say();//正常访问了

    say();//报错了

    这样我们发现 window 就无法访问到 say 方法了,此时 say 方法只属于 Person 对象独有

    的方法。很好的解决了封装破坏的情况。

    什么是原型

    上面我们看了基于 prototype 创建对象的方式很好的解决了我们前面遇到的一系列问题,

    那么到底什么是原型,原型又是如何解决如上的问题的呢?我们下面来研究研究。

    原型是 js 中非常特殊一个对象,当一个函数创建之后,会随之就产生一个原型对象,当

    通过这个函数的构造函数创建了一个具体的对象之后,在这个具体的对象中就会有一个属性

    指向原型。这就是原型的概念。

    鉴于原型的概念比较难以理解,我们就以上面的代码为例,画图为大家讲解:

    //第一种状态

    //定义了一个对象

    function Person() {

    }

    //第二种状态,这样赋值就会赋在原型对象中

    //使用原型来给对象赋值

    //这样就讲一个对象的属性和方法放在了该对象的原型中

    //外界是无法访问到这样数据的

    Person.prototype.name = "小妖的八戒";

    Person.prototype.age = 18;

    Person.prototype.say = function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    //第三种状态

    var p1 = new Person();

    //此时调用的是原型中的,因为自己中没有这些属性和方法

    p1.say();//正常访问了

    // say();//报错了

    //可以通过如下的方式检测p1是不是指向Person的原型对象

    // alert(Person.prototype.isPrototypeOf(p1))

    var p2 = new Person();

    p2.name = "张三";

    p2.age = 20;

    p2.say();

    常见的原型检测方式

    可以通过如下的方式检测p1是不是指向Person的原型对象

    alert(Person.prototype.isPrototypeOf(p1))

    //检测p1的构造器是否指向Person对象

    alert(p1.constructor == Person)

    //检测某个属性是不是自己内存中的

    alert(p1.hasOwnProperty("name"));

    alert(p2.hasOwnProperty("name"))

    同样我们可以使用 delete 语句来删除我们赋予对象的自己属性(注意:原型中的是无法

    删除的),如

    //可以使用delete语句删除对象中自己的属性,那么就会找到原型中的值

    delete p2.name;

    p2.say();

    alert(p2.hasOwnProperty("name"));

    检测在某个对象自己或者对应的原型中是否存在某个属性。

    alert("name" in p1);//true

    delete p2.name;//虽然删除了自己的name属性,但是原型中有

    alert("name" in p2);//true

    //原型和自己中都没有sex属性

    alert("sex" in p1);//false

    那么问题来了?如果检测只在原型中,不在自己中的属性呢?(提问)

    //我们可以自己写代码来测试属性不在自己,在原型中

    function hasPrototypeProperty(obj,prop) {

    if (!obj.hasOwnProperty(prop)) {

    if (prop in obj) {

    return true;

    }

    }

    return false;

    }

    alert(hasPrototypeProperty(p1,"name"));

    原型重写

    在上面的写法中,我们已经解决了大量的问题,使用原型。但是如果我们的对象中存在

    大量的属性或者方法的时候,使用上面的方式,感觉要写大量的【对象.prototype.属性名 】

    这样的代码,感觉不是很好,那么我们可以使用 json 的方式来写:

    function Person() {

    }

    Person.prototype = {

    name : "马帅哥",

    age : 18,

    say : function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    }

    var p1 = new Person();

    p1.say()

    var p2 = new Person();

    p2.name = "张三";

    p2.age = 20;

    p2.say();

    但是这种写法,我们是将该对象的原型覆盖(注意:这两种写法不一样的,第一种是扩

    充,第二种是覆盖),就会出现如下的问题:

    function Person() {

    }

    Person.prototype = {

    constructor:Person,//手动指向Person

    name : "马帅哥",

    age : 18,

    say : function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    }

    var p1 = new Person();

    p1.say()

    var p2 = new Person();

    p2.name = "张三";

    p2.age = 20;

    p2.say();

    //此时p1的构造器不在指向Person,而是指向了Object

    //因为我们覆盖了Person的原型,所以如果constructor比较重要的话,

    //我们可以手动指向

    alert(p1.constructor == Person)

    此时就没有问题了。但是原型重写会给我们带来一些非常有趣的现象。下面我们来研究

    研究。

    function Person() {

    }

    var p1 = new Person();

    Person.prototype.sayHello = function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    // p1.sayHello();

    Person.prototype = {

    constructor:Person,//手动指向Person

    name : "马帅哥",

    age : 18,

    say : function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    }

    var p2 = new Person();

    p2.name = "张三";

    p2.age = 20;

    p1.sayHello();//此时找不到name和age,但是代码正确

    p2.say();//正确

    p1.say();//错误,因为原型重写了

    为了解决原型所带来的问题,需要通过组合构造函数和原型来实现对象的创建将:属性

    在构造函数中定义,将方法在原型中定义。这种有效集合了两者的优点,是目前最为常用的

    一种方式。

    //属性在构造方法定义

    function Person(name,age,friends) {

    this.name = name;

    this.age = age;

    this.friends = friends;

    }

    /**

    * 此时所有的属性都是保存在自己的内存中

    * 方法都是定义在prototype(原型)中

    */

    //方法在原型中定义

    Person.prototype = {

    constructor:Person,

    say : function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");

    }

    };

    var p1 = new Person("马帅哥",18,["老宗","老万"]);

    p1.friends.push("老马");

    alert(p1.friends);

    var p2 = new Person("曾小贤",20,["小丽","小美"]);

    alert(p2.friends);

    写了那么多的代码,目的就是为了这种定义 javascript 对象的方式,所以我们最终的定

    义 javascript 对象的方案就是基于组合的方式定义,将属性的定义放在构造函数中,将方法

    的定义放在原型中。

    终极方案—基于动态原型的对象定义(选学)

    上面的方案在我们看来已经相当的完美了,但是因为一些面向对象的程序员(如:java、

    c#)等开发人员他们认为将方法放在外面不像面向对象的写法,所以提供了另一种写法,在

    这里说说,经供参考:

    //属性在构造方法定义

    function Person(name,age,friends) {

    this.name = name;

    this.age = age;

    this.friends = friends;

    //判断不存在的时候,如果存在就不在写,内存减少消耗

    if (!Person.prototype.say) {

    Person.prototype.say = function() {

    alert("我的名字是:"+this.name+",我今年"+this.age+"岁了");}}}

    var p1 = new Person("马帅哥",18,["老宗","老万"]);

    p1.friends.push("老马");

    alert(p1.friends);

    var p2 = new Person("曾小贤",20,["小丽","小美"]);

    alert(p2.friends);

    构造出的函数都有一个一个属性:Prototype:原型 对象.prototype具有constructor属性

    通过json覆盖创建的对象没有constructor属性。

     

    相关文章

      网友评论

        本文标题:面向对象

        本文链接:https://www.haomeiwen.com/subject/nagjcxtx.html