Javascript中的面向对象入门实例(二)

前端之家收集整理的这篇文章主要介绍了Javascript中的面向对象入门实例(二)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
严格来说,Javascript并不是一门面向对象的语言,因为它没有原生提供完整的面向对象机制。但它的语言特性又允许我们去模拟大部分这些机制。

new操作符

Javascript中也用new操作符创建类的实例,例如:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
var arr = new Array(10); // 创建长度为10的数组
var date = new Date; // 不需要传参数给构造函数时,括号可以省略
与C#不同的是,Javascript中new的是函数而不是class,这个函数即为类的构造函数。在构造函数中,可以声明属性方法。例如:

function Square() {
    this.length = 1;
    this.perimeter = function() {
        return this.length * 4;
    };
}

var square = new Square();
square.perimeter(); // 4
square.length = 10;
square.perimeter(); // 40
虽然上面的代码看上去很正常,但实际上存在一个缺陷:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
var square1 = new Square();
var square2 = new Square();
square1.perimeter === square2.perimeter; // false
由于每次创建Square对象执行构造函数时都创建了新的函数perimeter,所以多个Square对象的perimeter并非指向同一个函数。这样会造成内存的浪费。要解决这个问题,可以使用原型链。

原型链

Javascript中的对象都有一个内部属性__proto__(在新浏览器中已经变成可读可写的外部属性了)指向其构造函数的prototype;构造函数的prototype也是一个对象,所以它也有一个__proto__属性指向它的构造函数的prototype……如此逐层查找,直到__proto__属性的值是null为止。这就是原型链。下面以代码证实这一规则:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
var arr = new Array();
arr.__proto__ === Array.prototype; // true
那么Array.prototype.__proto__又是什么呢?在控制台打印出来可以发现,是一个Object对象,所以:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
Array.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__; // null
Object类是所有类型的基类,所以它的prototype的__proto__属性为null。最后得出arr的原型链为:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
arr.__proto__
Array.prototype
Array.prototype.__proto__
an Object object
object.__proto__
Object.prototype
Object.prototype.__proto__
null
原型链的用处在于:当访问对象的某个属性方法时,如果对象本身没有这个属性方法,就会在原型链中由浅到深进行查找(浅处优先)。同样地有代码可以证实这一规则:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
var arr = new Array();
// arr本身没有push
arr.hasOwnProperty('push'); // false
// 访问arr.push时实际上访问的是Array.prototype.push。
arr.push === arr.__proto__.push; // true
arr.push === Array.prototype.push; // true
不难发现,即使有多个Array对象,它们也是共享Array.prototype上的方法。所以,借助原型链就可以解决前面提到的多个函数副本浪费内存的问题:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
function Square() {
    this.length = 1;
}
Square.prototype.perimeter = function() {
    return this.length * 4;
};

var square1 = new Square();
var square2 = new Square();
square1.perimeter === square2.perimeter; // true

继承

原型链的层次结构跟面向对象的继承非常相似,所以原型链也可以用于模拟继承。先来看一个简单的例子:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
function Shape() {
    this.name = '形状';
}
Shape.prototype.perimeter = function() { };

function Square() {
    this.length = 1;
}
// Javascript中没有抽象类的概念,所以Shape是可以实例化的
Square.prototype = new Shape(); // important
Square.prototype.perimeter = function() {
    return this.length * 4;
};

var square = new Square();
square.name; // '形状'
square.perimeter(); // 4
其中最为关键的就是把一个Shape对象加入到Square的原型链中去。根据原型链的规则:
访问square.name时,实际上访问的是Square.prototype.name,也就是那个Shape对象的name属性

调用square.perimeter()时,虽然Square.prototype和Shape.prototype中都存在perimeter方法,但根据浅处优先原则,调用的是Square.prototype.perimeter。
很明显,这跟继承的效果是一样的。

封装

目前Javascript没有任何机制可以控制属性方法的可访问性,也就是说private和protected都无法实现。但是利用闭包的特性可以实现类似private的效果

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
function Square() {
    var length = 1;
    this.setLength = function(len) {
        length = len;
    };
    this.getLength = function() {
        return length;
    };
    this.perimeter = function() {
        return length * 4;
    };
}

var square = new Square();
square.perimeter(); // 4
square.setLength(10);
square.perimeter(); // 40
由于length变量的作用域是在Square内,所以外部无法访问,也就达到了私有的效果。但这样一来,三个方法就不能写到Square.prototype上,因为一旦它们脱离了Square的作用域,就无法访问length了。

鉴于封装特性难以实现,实际工作中更多是通过编码规范来进行约束。例如可以规定:名字以双下划线开头的属性方法是private的,名字以单下划线开头的属性方法是protected的。代码示例:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
function Square() {
    this.__length = 1;
}
Square.prototype.setLength = function(len) {
    this.__length = len;
};
Square.prototype.getLength = function() {
    return this.__length;
};
Square.prototype.perimeter = function() {
    return this.__length * 4;
};

var square = new Square();
square.perimeter();
square.setLength(10);
square.perimeter();

小结

最后,我们再来了解一下new操作符的大概执行过程:

创建一个新的空对象;
设置新对象的__proto__属性为构造函数的prototype;
以新对象为this执行构造函数

如果以代码表示,则为:

/**
 * 面向对象入门实例(二)
 *
 * @param 
 * @arrange (512.笔记) jb51.cc
 **/
// 以new Foo()为例
var obj = { };
obj.__proto__ = Foo.prototype;
Foo.call(obj);
大家不妨以此过程回顾前面的代码段。

本篇到此结束,下一篇讲述在Javascript中模拟面向对象的其他细节问题。 原文链接:https://www.f2er.com/js/527695.html

猜你在找的JavaScript相关文章