• 彻底研透javascript中的对象及面向对象编程


    1、什么是对象、对象的属性、方法

    对象是由一些变量和函数组成的一个集合,我们将这些变量和函数称之为对象里面的属性和方法,如下是一个自定义对象单例(对象直接量)的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var car = {
    brand : 'BMW',//品牌
    model : 'X3',//型号
    endurance : '600km',//续航里程
    oil : 30,//邮箱油量
    getBrand : function() {
    alert(this.brand);
    },
    getOil: function(){
    alert(this.oil);
    },
    addOil: function(n) {
    this.oil += n;
    }
    };

    我们可以通过以下一些代码访问对象成员:

    1
    2
    3
    4
    car.brand;//访问car的品牌
    car.getBrand();//输出car的品牌
    car.addOil(1);//添加car的油量
    car.getOil();//获得car的油量

    此示例代码地址:

    在javascript其实我们一直都在使用对象,当我们这样使用字符串的方法时:

    1
    myString.split(',');

    当这样访问document对象时:

    1
    2
    var myDiv = document.createElement('div');
    var myVideo = document.querySelector('video');

    javascript还有很多内建对象,如:Array、Math、Date等。

    2、this的指向

    在上面car的对象定义中,我们用到了this。
    this 指向了代码所在的对象(代码运行时所在的对象),在对象直接量里this看起来不是很有用,但是当你动态创建一个对象(例如使用构造器)时它是非常有用的,之后你会更清楚它的用途。关于this的更详细文章:彻底领悟javascript中的this

    3、创建对象的几种方式

    除了1中直接用对象直接量创建对象,我们还有以下几种方式:

    • 通过构造函数创建
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      function Car(params) {
      this.brand=params.brand;//品牌
      this.model=params.model;//型号
      this.endurance=params.endurance;//续航里程
      this.oil=params.oil;//邮箱油量
      this.getBrand=function() {
      alert(this.brand);
      },
      this.getOil=function(){
      alert(this.oil);
      },
      this.addOil=function(n) {
      this.oil += n;
      }
      }

      var Car1 = new Car({
      brand : 'BMW',//品牌
      model : 'X3',//型号
      endurance : '600km',//续航里程
      oil : 30,//邮箱油量
      });
      var Car2 = new Car({
      brand : 'BYD',//品牌
      model : '元',//型号
      endurance : '500km',//续航里程
      oil : 50,//邮箱油量
      });
    • 通过Object()构造函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var car1 = new Object();//空对象
    car.brand = "BMW";//成员赋值

    var car2 = new Object({//构造时就填充属性和方法
    brand : 'BMW',//品牌
    model : 'X3',//型号
    endurance : '600km',//续航里程
    oil : 30,//邮箱油量
    getBrand : function() {
    alert(this.brand);
    },
    getOil: function(){
    alert(this.oil);
    },
    addOil: function(n) {
    this.oil += n;
    }
    })
    • 通过Object对象的create()方法
      通过这种方式创建的属性和方法在原型对象上,不可枚举。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      var car1 = new Object({//构造时就填充属性和方法
      brand : 'BMW',//品牌
      model : 'X3',//型号
      endurance : '600km',//续航里程
      oil : 30,//邮箱油量
      getBrand : function() {
      alert(this.brand);
      },
      getOil: function(){
      alert(this.oil);
      },
      addOil: function(n) {
      this.oil += n;
      }
      });
      //以 car1 为原型对象创建了 car2 对象。car2.__proto__指向的即是car1
      var car2 = Object.create(car1);//以car1为基础创建car2,它们具有相同的属性和方法,但属于不同的引用,创建的属性和方法在原型对象上,不可枚举

    4、原型对象(prototype)

    每个对象拥有一个原型对象(prototype),对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。因为prototype是函数的一个特殊属性,而不是对象的。
    在传统的 OOP 中(如:java),首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是proto属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。
    Object.getPrototypeOf(new Foobar())和Foobar.prototype指向着同一个对象,都是指向Foobar构造函数的prototype。

    在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype) 。打开一个控制台 (在Chrome和Firefox中,可以按Ctrl+Shift+I来打开)切换到”控制台” 选项卡, 复制粘贴下面的JavaScript代码,然后按回车来运行.

    1
    2
    3
    4
    5
    6
    7
    function doSomething(){}
    console.log( doSomething.prototype );
    // It does not matter how you declare the function, a
    // function in javascript will always have a default
    // prototype property.
    var doSomething = function(){};
    console.log( doSomething.prototype );

    正如上面所看到的, doSomething 函数有一个默认的原型属性,它在控制台上面呈现了出来. 运行这段代码之后,控制台上面应该出现了像这样的一个对象.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    constructor: ƒ doSomething(),
    __proto__: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),
    isPrototypeOf: ƒ isPrototypeOf(),
    propertyIsEnumerable: ƒ propertyIsEnumerable(),
    toLocaleString: ƒ toLocaleString(),
    toString: ƒ toString(),
    valueOf: ƒ valueOf()
    }
    }

    现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示.

    1
    2
    3
    function doSomething(){}
    doSomething.prototype.foo = "bar";
    console.log( doSomething.prototype );

    结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    foo: "bar",
    constructor: ƒ doSomething(),
    __proto__: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),
    isPrototypeOf: ƒ isPrototypeOf(),
    propertyIsEnumerable: ƒ propertyIsEnumerable(),
    toLocaleString: ƒ toLocaleString(),
    toString: ƒ toString(),
    valueOf: ƒ valueOf()
    }
    }

    然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething 的实例。正确使用 new 运算符的方法就是在正常调用函数时,在函数名的前面加上一个 new 前缀. 通过这种方法,在调用函数前加一个 new ,它就会返回一个这个函数的实例化对象. 然后,就可以在这个对象上面添加一些属性.

    1
    2
    3
    4
    5
    function doSomething(){}
    doSomething.prototype.foo = "bar"; // add a property onto the prototype
    var doSomeInstancing = new doSomething();
    doSomeInstancing.prop = "some value"; // add a property onto the object
    console.log( doSomeInstancing );

    结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {
    prop: "some value",
    __proto__: {
    foo: "bar",
    constructor: ƒ doSomething(),
    __proto__: {
    constructor: ƒ Object(),
    hasOwnProperty: ƒ hasOwnProperty(),
    isPrototypeOf: ƒ isPrototypeOf(),
    propertyIsEnumerable: ƒ propertyIsEnumerable(),
    toLocaleString: ƒ toLocaleString(),
    toString: ƒ toString(),
    valueOf: ƒ valueOf()
    }
    }
    }

    就像上面看到的, doSomeInstancing 的 proto 属性就是doSomething.prototype. 但是这又有什么用呢? 好吧,当你访问 doSomeInstancing 的一个属性, 浏览器首先查找 doSomeInstancing 是否有这个属性. 如果 doSomeInstancing 没有这个属性, 然后浏览器就会在 doSomeInstancing 的 proto 中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 proto 有这个属性, 那么 doSomeInstancing 的 proto 上的这个属性就会被使用. 否则, 如果 doSomeInstancing 的 proto 没有这个属性, 浏览器就会去查找 doSomeInstancing 的 proto 的 proto ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 proto 就是 window.Object.prototype. 所以 doSomeInstancing 的 proto 的 proto (也就是 doSomething.prototype 的 proto (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 proto 的 proto 的 proto 里面查找. 然而这有一个问题: doSomeInstancing 的 proto 的 proto 的 proto 不存在. 最后, 原型链上面的所有的 proto 都被找完了, 浏览器所有已经声明了的 proto 上都不存在这个属性,然后就得出结论,这个属性是 undefined.(这段很拗口,但是对理解prototype是怎么运行的非常有用,建议看不懂的多读几遍,好好理解一下。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function doSomething(){}
    doSomething.prototype.foo = "bar";
    var doSomeInstancing = new doSomething();
    doSomeInstancing.prop = "some value";
    console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
    console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
    console.log("doSomething.prop: " + doSomething.prop);
    console.log("doSomething.foo: " + doSomething.foo);
    console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
    console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);

    结果:

    1
    2
    3
    4
    5
    6
    doSomeInstancing.prop:      some value
    doSomeInstancing.foo: bar
    doSomething.prop: undefined
    doSomething.foo: undefined
    doSomething.prototype.prop: undefined
    doSomething.prototype.foo: bar

    修改原型:
    我们从下面这个例子来看一下如何修改构造器的 prototype 属性。
    在已有的Car构造函数的定义后面,增加以下这段代码,它将为构造器的 prototype 属性添加一个新的方法:

    1
    2
    3
    4
    5
    6
    function Car(){
    //......
    };
    Car.prototype.setPrice = function(price){
    this.price = '10万';
    }

    这样定义后,所有Car的实例对象都具有了setPrice()这个方法,包括在这个方法定义之前创建的实例对象(这就是前面讲的原型链的原理)。
    我们一般通过prototype添加方法,不推荐使用它来添加属性。
    事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 构造器及其属性定义

    function Test(a,b,c,d) {
    // 属性定义
    };

    // 定义第一个方法

    Test.prototype.x = function () { ... }

    // 定义第二个方法

    Test.prototype.y = function () { ... }

    // 等等……

    5.proto 中的constructor 属性

    每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。某些情况下,如果我们找不到某个对象的构造函数的引用,又希望能继续创建一个同类型的对象,我们可以使用该对象中的_proto_中的constructor属性来创建,假如有一个对象car1,那么我们可以用如下的方式创建car2

    1
    var car2 = new car1.constructor();//注意:_proto_中的属性都可以通过实例对象直接访问

    除此之外,我们还可以通过constructor属性获得某个对象实例的构造器的名字,如下:

    1
    var constructorName = car1.constructor.name;//constructor的name属性为构造器的名字

    6、ES6中Class关键字定义类

    ECMAScript6 引入了一套新的关键字用来实现 class。ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。这些新的关键字包括 class, constructor,static,extends 和 super。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    "use strict";

    class Polygon {
    constructor(height, width) {
    this.height = height;
    this.width = width;
    }
    }

    class Square extends Polygon {
    constructor(sideLength) {
    super(sideLength, sideLength);
    }
    get area() {
    return this.height * this.width;
    }
    set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
    }
    }

    var square = new Square(2);

    Class的取值函数(getter)和存值函数(setter):
    与ES5一样,在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class MyClass {
    constructor() {
    // ...
    }
    get prop() {
    return 'getter';
    }
    set prop(value) {
    console.log('setter: '+value);
    }
    }

    let inst = new MyClass();

    inst.prop = 123;
    // setter: 123

    inst.prop
    // 'getter'

    上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。

    Class 的静态方法:

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Foo {
    static classMethod() {
    return 'hello';
    }
    }

    Foo.classMethod() // 'hello'

    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function

    上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

    父类的静态方法,可以被子类继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Foo {
    static classMethod() {
    return 'hello';
    }
    }

    class Bar extends Foo {
    }

    Bar.classMethod(); // 'hello'

    上面代码中,父类Foo有一个静态方法,子类Bar可以调用这个方法。

    静态方法也是可以从super对象上调用的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Foo {
    static classMethod() {
    return 'hello';
    }
    }

    class Bar extends Foo {
    static classMethod() {
    return super.classMethod() + ', too';
    }
    }

    Bar.classMethod();

    Class的静态属性和实例属性:
    静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。

    1
    2
    3
    4
    5
    class Foo {
    }

    Foo.prop = 1;
    Foo.prop // 1

    目前,只有这种写法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 以下两种写法都无效
    class Foo {
    // 写法一
    prop: 2

    // 写法二
    static prop: 2
    }

    Foo.prop // undefined

    7、javascript中的继承

    • ES5的通过修改原型链实现继承
    • function superClass(){ 
      this.value = “super”; 
      }
      
      superClass.prototype.getSuperValue = function(){ 
      return this.value; 
      } 
        
      function subClass(){ 
      this.subClassValue = “sub”; 
      }
      
      subClass.prototype = new superClass(); 
      subClass.prototype.getSubValue = function(){ 
      return this.subClassValue; 
      }
      
        
      var s = new subClass(); 
      alert(s.getSuperValue()); 

      subClass.prototype = new superClass(); subClass.prototype 指向superClass的实例意味着什么呢,意味着

      subClass.prototype指向了superClass的prototype,所以就能访问到原型中的属性和方法。
      ————————————————
      版权声明:本文为CSDN博主「非著名coder」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
      原文链接:https://blog.csdn.net/houyaowei/java/article/details/51444145

    • ECMAScript6中Class的继承

    Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

    1
    class ColorPoint extends Point {}

    上面代码定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类。下面,我们在ColorPoint内部加上代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class ColorPoint extends Point {
    constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
    }

    toString() {
    return this.color + ' ' + super.toString(); // 调用父类的toString()
    }
    }

    上面代码中,constructor方法和toString方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

    子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

    1
    2
    3
    4
    5
    6
    7
    8
    class Point { /* ... */ }

    class ColorPoint extends Point {
    constructor() {
    }
    }

    let cp = new ColorPoint(); // ReferenceError

    上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。

    ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

    如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

    1
    2
    3
    constructor(...args) {
    super(...args);
    }

    另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Point {
    constructor(x, y) {
    this.x = x;
    this.y = y;
    }
    }

    class ColorPoint extends Point {
    constructor(x, y, color) {
    this.color = color; // ReferenceError
    super(x, y);
    this.color = color; // 正确
    }
    }

    上面代码中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法之后就是正确的。

    下面是生成子类实例的代码。

    1
    2
    3
    4
    let cp = new ColorPoint(25, 8, 'green');

    cp instanceof ColorPoint // true
    cp instanceof Point // true

    上面代码中,实例对象cp同时是ColorPoint和Point两个类的实例,这与ES5的行为完全一致。

  • 相关阅读:
    C# MVC 自定义ActionResult实现EXCEL下载
    如何让WEBAPI 能够进行跨越访问
    C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解
    sC#进阶系列——WebApi 接口参数不再困惑:传参详解
    mybatis.net insert 返回主键
    [转]MySQL中timestamp数据类型的特点
    [转]java List和数组相互转换方法
    [转]Mybatis foreach 批量操作
    [转]让iframe自适应高度-真正解决
    [转]decorators.xml的用法
  • 原文地址:https://www.cnblogs.com/niejunchan/p/12866369.html
Copyright © 2020-2023  润新知