面向对象

Posted sherrycat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象相关的知识,希望对你有一定的参考价值。

一、对象的定义

在ECMAscript-262中,对象被定义为“无序属性的集合,其属性可以包含基本值、对象或者函数”。无序  键值对

也就是说,在javascript中,对象就是由一些无序的key-value对组成。

1.创建对象

var obj1 = new Object();

var obj2 = {};

var person1 = {};
person.name = "TOM";
person.getName = function() {
    return this.name;
}

var person2 = {
    name: "TOM",
    getName: function() {
        return this.name;
    }
}

以上四种方法都可以创建一个对象。

2.访问对象的属性和方法

var person = {
    name: ‘TOM‘,
    age: ‘20‘,
    getName: function() {
        return this.name
    }
}

//访问对象的属性或方法

person.name
// 或者
person[‘name‘]

当访问的属性名是个变量的时候,常常使用第二种方式。

比如,如果要同时访问name属性和age属性

[‘name‘, ‘age‘].forEach(function(item) {
    console.log(person[item]);
})

二、工厂模式

有时候对象很相似,只有一两个属性不同的时候,为了避免写过多重复性代码,就会用工厂模式的方式解决问题。

相当于提供一个模子,通过模子复制出需要的对象。

var createPerson = function(name, age) {

    // 声明一个中间对象,该对象就是工厂模式的模子
    var o = new Object();

    // 依次添加我们需要的属性与方法
    o.name = name;
    o.age = age;
    o.getName = function() {
        return this.name;
    }

    return o;
}

// 创建两个实例
var perTom = createPerson(‘TOM‘, 20);
var PerJake = createPerson(‘Jake‘, 22);

像这样就能创建很多个person对象。

但是有一个问题,没有办法识别对象实例的类型。

可以使用instanceof来识别。

instanceof运算符用来检测constructor.prototype是否存在于参数object的原型链上。

因此在工厂模式的基础上,需要使用构造函数的方式来解决这个麻烦。

三、构造函数

复习new操作:

  • 创建一个新的对象;
  • 将构造函数的this指向这个新对象;
  • 指向构造函数的代码,为这个对象添加属性,方法等;
  • 返回新对象。

   new关键字用代码实现:

// 先一本正经的创建一个构造函数,其实该函数与普通函数并无区别
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        return this.name;
    }
}

// 将构造函数以参数形式传入
function New(func) {

    // 声明一个中间对象,该对象为最终返回的实例
    var res = {};
    if (func.prototype !== null) {

        // 将实例的原型指向构造函数的原型
        res.__proto__ = func.prototype;
    }

    // ret为构造函数执行的结果,这里通过apply,将构造函数内部的this指向修改为指向res,即为实例对象
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));

    // 当我们在构造函数中明确指定了返回对象时,那么new的执行结果就是该返回对象
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }

    // 如果没有明确指定返回对象,则默认返回res,这个res就是实例对象
    return res;
}

// 通过new声明创建实例,这里的p1,实际接收的正是new中返回的res
var p1 = New(Person, ‘tom‘, 20);
console.log(p1.getName());

// 当然,这里也可以判断出实例的类型了
console.log(p1 instanceof Person); // true

一般构造函数的首字母大写。

四、原型

工厂模式的第一个弊端是没有办法识别对象实例的类型。(所以需要用构造函数的方式来解决,用instanceof)

工厂模式的第二个弊端是,当声明了100个person对象,每个对象中的getName方法都是一模一样的,但是由于它们分别属于不同的实例,就不得不一直不停地为getName分配空间。

所以,想让每一个实例对象都访问同一个方法

我们创建的每一个函数,都有一个prototype属性,这个属性指向一个对象。这个对象,就是原型。

当我们在创建对象时,可以根据自己的需求,选择性地将一些属性和方法通过prototype属性,挂载在原型对象上。(之前一直不太明白构造函数和原型的作用是什么)

而每个new出来的实例,都有一个__proto__属性,该属性指向构造函数的原型对象。

通过__proto__属性,可以让实例对象访问原型对象上的方法。

因此,当所有的实例都能通过__proto__属性访问到原型对象时,原型对象的方法与属性就变成了共有方法和属性。

举例:

// 声明构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 通过prototye属性,将方法挂载到原型对象上
Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person(‘tim‘, 10);
var p2 = new Person(‘jak‘, 22);
console.log(p1.getName === p2.getName); // true

技术图片

 

 通过图可以看出,构造函数的prototype属性与实例对象的__proto__属性指向同一个对象,在这个原型对象中,有原型的constructor属性,这个属性指向构造函数,还有通过prototype属性挂载的方法。

new关键字让构造函数具有了与普通函数不同的许多特点。new的过程中,执行了如下过程:

1.声明一个中间对象

2.将该中间对象的原型指向构造函数的原型

3.将构造函数的this,指向该中间对象

4.返回这个中间对象,即返回实例对象

可以知道,中间对象中的属性与方法都是在构造函数中添加的,通过this声明的属性与方法称为私有属性与方法,它们被当前某一个实例对象独有。

而通过原型声明的属性和方法,称之为共有属性与方法,可以被所有实例对象访问。

当我们访问实例对象中的属性和方法时,会优先访问实例对象自身的属性和方法,也就是私有属性与方法。

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.getName = function() {
        console.log(‘this is constructor.‘);
    }
}

Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person(‘tim‘, 10);

p1.getName(); // this is constructor.

可以看出,原型中的方法没有被访问。

可以用in来判断一个对象是否有用某个属性和方法*(无论该属性/方法存在于实例对象还是原型对象)

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

Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person(‘tim‘, 10);

console.log(‘name‘ in p1); // true

in还有一个常见的应用就是判断当前页面是否能在移动端打开:

isMobile = ‘ontouchstart‘ in document;

// 很多人喜欢用浏览器UA的方式来判断,但并不是很好的方式

两种原型写法:

//以前的写法
function Person() {}

Person.prototype.getName = function() {}
Person.prototype.getAge = function() {}
Person.prototype.sayHello = function() {}
... ...

//更简单的写法
function Person() {}

Person.prototype = {
    constructor: Person,
    getName: function() {},
    getAge: function() {},
    sayHello: function() {}
}

这个Person.prototype={}其实并不是之前图中写到的Person.prototype,而是重新创建了一个{}对象,并赋值给Person.prototype,而创建的这个{}也不是最初那个原型对象。所以它并不包含constructor属性,所以必须要手动设置constructor的指向。

五、原型链

原型对象其实也是普通对象,几乎所有的对象都有可能是原型对象,也可能是实例对象,也可以同时是实例对象,同时是原型对象。这样的一个对象,正式构成原型链的一个节点。

拿toString方法举例。

这个方法是从哪里来的?

先随意声明一个函数:

function add() {}

可以用如下图片表示该函数的原型链:

技术图片

 

 可以看出,add是Function的实例,而Function的原型对象同时又是Object原型的实例。实例.__proto__==构造函数.prototype。

原型链和作用域链类似,都是一个单向的查找过程。因此,实例对象能够通过原型链,访问到处于原型链上对象的所有属性和方法。所以,add能够访问到处于Object原型对象上的toString方法。

 

下一章再写继承。

以上是关于面向对象的主要内容,如果未能解决你的问题,请参考以下文章

面向面试编程代码片段之GC

PHP面向对象之选择工厂和更新工厂

Java中面向对象的三大特性之封装

python之路之前没搞明白4面向对象(封装)

Scala的面向对象与函数编程

Python面向对象学习之八,装饰器