# JS面对对象基础

开篇我们先明确几个概念:

**构造函数:**函数中的一种,通过关键字new可以创建其实例。为了便于区分,通常首字母大写;

原型对象:一种特殊的对象,构造函数创建时即自动生成;与构造函数形成一一对应,如同人和影子般的关系;

实例:通过构造函数new实例化出来的对象;

# 创建对象的方式

# 1】new 操作符 + Object 创建对象

var person = new Object();
person.name = "lisi";
person.age = 21;
person.family = ["lida","lier","wangwu"];
person.say = function(){
  alert(this.name);
}

这种方式直接使用Object类对象来创建实例,会产生很多重复代码。

# 2】字面式创建对象

var person = {
  name: "lisi"
  age: 21,
  family: ["lida","lier","wangwu"],
  say: function(){
    alert(this.name);
  }
};

使用js对象字面量创建实例对象,同理也会产生很多重复代码。

# 3】工厂模式

在上面 new Object类对象的方式 和 字面式的方式 的基础上诞生了工厂模式,可以创建多个类似对象,减少重复代码。但是无法识别对象类型功能。(因为创建出来的实例对象都是基于Object类对象直接创建的,原型链上只有Object对象

function createPerson(name,age,family){
  var o = new Object()
  o.name = name
  o.age = age
  o.family = family
  o.say = function(){
    alert(this.name);
  }
  return o
}
var person1 =  createPerson("lisi",21,["lida","lier","wangwu"]);   
//instanceof无法判断它是谁的实例,只能判断他是对象,构造函数都可以判断出
var person2 =  createPerson("wangwu",18,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true 只能判断是原型链上有Object

//对象字面量工厂模式
function createPerson(name,age,family){
  var o = {}
  o.name = name
  o.age = age
  o.family = family
  o.say = function(){
    alert(this.name);
  }
  return o
}

# 4】构造函数模式

构造函数的作用是给实例添加各种属性,形成具有自己独有属性的实例

function Person(name,age,family){
  this.name = name
  this.age = age
  this.family = family
  this.say = function (){
    alert(this.name)
  }
}

var person1 = new Person("lisi",21,["lida","lier","wangwu"]);
var person2 = new Person("lisi",21,["lida","lier","lisi"]);
console.log(person1 instanceof Object); //true
console.log(person1 instanceof Person); //true
console.log(person2 instanceof Object); //true
console.log(person2 instanceof Person); //true

console.log(person1.__proto__)//Person {}
//指向创建person1时的Person原型对象,这个原型对象上有个constructor属性
console.log(person1.__proto__.constructor);//[Function: Person]
console.log(person1.__proto__.constructor.prototype)//Person {}
//Person构造函数里面又有一个prototype属性指向Person的原型对象
//此时Person构造函数和Person原型对象就形成了一个环状结构

console.log(person1.constructor);//[Function: Person]
//访问person1实例对象上的属性,会先去person1的本身中取找属性,再去原型对象上找属性。

原型链

对比工厂模式有以下不同之处:

1、没有显式地创建对象(使用的是函数对象)

2、直接将属性和方法赋给了 this 对象

3、没有 return 语句(因为使用了new来创造实例对象,并不是直接调用构造函数)

以此方法调用构造函数步骤:(new的过程)

1、创建一个新对象

2、将构造函数的作用域赋给新对象(将this指向这个新对象)

3、执行构造函数代码(为这个新对象添加属性)

4、返回新对象 (Person原型对象的指针赋给实例对象person)

可以看出,构造函数知道自己从哪里来(通过 instanceof 可以看出其既是Object的实例,又是Person的实例)

构造函数也有其缺陷,每个实例都包含不同的Function实例( 构造函数内的方法在做同一件事,但是实例化后却产生了不同的对象,方法是函数 ,函数也是对象,构造函数里面的所有东西都会在new的时候被调用执行,创建新的对象地址去保存里面的属性和方法

因此产生了原型模式。

# 5】原型模式

  • 每个函数都有prototype属性,指向该函数原型对象(可以不写默认有)
  • 每个原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针(可以不写默认有)
function Person() {

}
Person.prototype.name = 'lisi'
Person.prototype.age = 21
Person.prototype.family = ["lida", "lier", "wangwu"]
Person.prototype.say = function () {
    alert(this.name);
};
console.log(Person.prototype);
//Person {
//  name: 'lisi',
//  age: 21,
//  family: [ 'lida', 'lier', 'wangwu' ],
//  say: [Function]
//}
console.log(Person.prototype.constructor);
//[Function: Person]
console.log(Person);
//[Function: Person]
//我们也可以看出来对象的构造函数和原型之间形成一个环

//我们使用new关键字来创建实例对象,就可以使用实例对象来直接访问原型上的属性
var person1 = new Person()
console.log(person1.name, person1.age, person1.family, person1.say);//访问原型对象里的属性
//lisi 21 [ 'lida', 'lier', 'wangwu' ] [Function]
person1.name='zhangsan'//此时我们通过实例给构造器中添加属性name='zhangsan'
console.log(person1.name);//zhangsan
//为什么我们会出现这种情况呢?
//当构造函数进行new实例化的时候,实例对象会对构造函数中的这些属性拷贝出一份副本:
//1.当实例调用属性name的的时候,会先从这个拷贝的属性副本中搜索name属性,如果构造函数中没有name属性,就去向原型对象中搜索name属性,
//2.如果原型对象中没有name属性,就会打印出undefined	

原型模式的好处是所有对象实例共享它的属性和方法(即所谓的共有属性),此外还可以如代码第16,17行那样设置实例自己的属性(方法)(即所谓的私有属性),可以覆盖原型对象上的同名属性(方法)。

# 6】混合模式(构造函数模式+原型模式)

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性

  • 规定:
    • 构造函数里面都只定义私有的属性和方法;
    • 原型对象上面定义共享的属性和方法;
    • 构造函数外直接给对象赋值和方法就是静态属性和方法
function sub(name) {//构造函数
    this.prototype = sub.prototype//每个函数都有prototype属性,指向该函数原型对象(可以不写默认有)
    this.name = name
    this.say = function () {
        console.log('constructor')
    }
}
sub.prototype = {//原型对象
    constructor: sub,//每个原型对象都有constructor属性,这是一个指向prototype属性所在函数的指针(可以不写默认有)
    age: 12,
    hey: function () {
        console.log('prototype')
    }
}
sub.say = function () {//静态方法:可直接使用类对象访问,实例访问不了
    console.log('static')
}
sub.address='chengdu'//静态属性:可直接使用类对象访问,实例访问不了

let person = new sub('李四')
//访问实例对象的属性,依然遵循一个原则:先去创建实例时拷贝的构造函数副本中寻找属性,再到原型对象中寻找属性
console.log(person.name, person.age)//李四 12
person.say()//constructor
person.hey()//prototype
sub.say()//static 类对象直接访问而实例访问不了
console.log(sub.address);//chengdu 类对象直接访问而实例访问不了

可以看出,混合模式共享着对相同方法的引用,又保证了每个实例有自己的私有属性。最大限度的节省了内存。

因此,当多个实例需要使用同一个属性或方法时,我们应该将该方法放于原型对象上,从而避免相同属性或方法多次独立存在于多个对象导致内存浪费。

高程中还提到了动态原型模式,寄生构造函数模式,稳妥构造函数模式。

在这里插入图片描述

# 总结创建实例对象的过程

new实例化的过程:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(将this指向这个新对象)
  3. 执行构造函数代码(为这个新对象添加属性,也就是说的构造函数中的属性拷贝成副本,将其赋给这个新对象)
  4. 返回新对象 (原型对象的指针赋给这个实例对象)

程序执行时,会自动生成一个原型对象(prototype)与之相关联。同时给该构造函数自动添加一个属性:prototype,该属性为指针,指向原型对象。同时,也给该原型对象添加一个属性:constructor,该属性也为指针,指向与其对应的构造函数。此时,原型对象中只会包含些默认的方法和属性。

new实例化完成后,所有实例均会与原型对象形成多对一的隐性关联关系。所有实例会共享原型对象的属性和方法,当然也包括constructor。当原型对象被添加一个属性或者方法后,均会被所有实例共享,即可以通过任意一个实例进行访问。如果原型对象的属性或方法与实例的属性或方法名称一致,则实例自身的属性或方法优先级高于原型对象上的

**总结:**构造函数中的属性和方法仅为声明和定义,一旦实例化工作完成后,实例对象自身的属性和方法与构造函数将不在存在关联关系。原型对象与实例对象形成“备胎”关系,当通过对象访问属性或方法时,程序会优先搜索对象本身的属性或方法,不存在才会访问原型对象的方法或者属性。

Last Updated: 8/5/2020, 5:39:29 PM