创建对象

什么是工厂模式 ?

封装一个函数 , 用来创建对象并给对象中特定的属性添加值 , 优点是可以循环调用 , 缺点是每一个创建出来的对象都是独立的 , 不能确定它是哪个类型的对象 ( 或者说是想要将哪个对象作为模板进行创建 )

function func(name, age) {
    var o = {
        "name" : name,
        "age" : age
    };
    return o;
}
var person = func("oswald", 24);
var person2 = func("oswald", 24);
// {name: "oswald", age: 24}
console.log(person === person2);
// false
console.log(person.__proto__ === Object.prototype);     // true
console.log(person2.__proto__ === Object.prototype);    // true
// 每个对象都是独立的并且指向 Object.prototype 的 , 不能辨别基于哪个对象为原型

什么是构造函数模式

构造函数模式和工厂模式的区别

构造函数模式就是通过构造函数来创建自定义类型的对象 , 创建出来的对象都指向了同一个构造函数的 prototype , 解决了工厂模式中不能识别对象模板的问题

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    };
    /*  没有显式的创建对象
        用 this 代替新对象
        没有 return 语句    */
}
var person1 = new Person("oswald", 24);
console.log(person1);    // {name: "oswald", age: 24}
console.log(person1.__proto__ === Person.prototype);    // true
// person1 指向了 构造函数 func.prototype
console.log(person1 instanceof Person);     // true
// person1 对象是构造函数 Person ( 也可以叫做 Person 类型 ) 的实例
console.log(person1 instanceof Object);     // true
// person1 对象也是 Object 类型的实例 

构造函数模式的缺点

但是 , 上面的构造函数模式也不是没有缺点的 : 单纯的构造函数模式创建对象的时候 , 对象中每个方法都会在实例对象上重新创建一次 , 也就是说每次都会创建一个看起来相同但是完全不是一个"方法"的方法

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    };
}
var person1 = new Person("oswald", 24);
var person2 = new Person("oswald", 24);
console.log(person1.sayName = person2.sayName);     // false
// 每次创建对象的时候 , 都会生成一个功能一样但是是不同 Function 实例的方法

什么是原型模式

  • 每个函数在创建的时候都会有一个 prototype 属性 , 这个属性指向一个对象
  • 所有以这个函数为构造函数创建的实例对象 , 都会连接到构造函数的 prototype 属性指向的这个对象上 , 并且可以访问这个对象的所有属性和方法 , 这个对象就叫做原型对象
  • 原型对象在创建的时候自带一个不可枚举的 constructor 属性 , 指向构造函数
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
    Person.prototype.sayName = function(){
        console.log(this.name);
    };
}

var person1 = new Person();
console.log(person1);
// {} 得到一个空对象 , 因为创建的属性和方法都在实例的原型对象上
console.log(Person.prototype);
// {name: "oswald", age: 24, sayName: f(...)}
console.log(person1.name);
// oswald 得到一个名字 , 因为实例对象可以从它的原型对象上查找属性和方法

var person2 = new Person();
console.log(person2.sayName === person1.sayName);   // true
// person1 和 person2 访问的 sayName 方法都是原型对象上的方法 , 不是它们自身的 , 这样就解决了构造函数模式多次创建方法实例的缺点

原型对象中的属性和方法会和实例对象自有的冲突吗

如果当前实例对象中已经有了想要查找的属性和方法 , 会直接使用实例对象的属性和方法 , 如果没有才去原型对象中查找

function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.name);  // oswald
person1.name = "yin";
console.log(person1.name);  // yin

实例对象怎么访问原型对象

  • 在火狐、谷歌等浏览器中提供了一个 __proto__ 的属性 , 可以访问它的原型对象
  • ECMAScript 5 中提供了 Object.getPrototypeOf( ) 方法可以检测并返回它的原型对象
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype);
// true
console.log(Object.getPrototypeOf(person1) === Person.prototype);
// true

原型对象和原型属性有什么区别

  • 实例对象 ( person1 ) 的原型对象 ( __proto__ ) 是创建当前对象的构造函数 ( Person ) 的原型属性 ( prototype ) , 个人用来区别访问途径 , 便于理解
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype);
// true
  • 实例对象可以查找它的原型对象 , 但是不能查找它自己的原型属性
function Person(){
}
var person1 = new Person();
person1.prototype = {
    name: "yin",
    age: 23
}
console.log(person1.name);  // undefined

怎么检测属性或者方法在实例对象中是否存在

使用继承自 Object 对象的 hasOwnProperty( ) 方法可以检车要查找的属性或方法是否来自实例对象而不是实例对象的原型对象

function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
console.log(person1.name);  // oswald
console.log(person1.hasOwnProperty("name"));
// false 当前查找到的 name 属性来自原型对象
person1.name = "yin";
console.log(person1.name);  // yin
console.log(person1.hasOwnProperty("name"));
// true 当前查找到的 name 属性来自实例对象

枚举对象中的属性和方法有哪些方法

  • for - in 遍历 , 会将当前对象和当前对象的原型对象上所有可以枚举的属性和方法都返回
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
person1.color = "red";
for( poop in person1 ){
    console.log(poop);
    // color, name, age
}
  • ECMAScript 5 的 Object.key( ) 方法 , 遍历当前对象的可枚举属性和方法 , 不会去找原型对象
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
person1.color = "red";
var p1keys = Object.key(person1);
console.log(p1keys);    // color
  • Object.getOwnPropertyNames( ) 方法 , 遍历当前对象的所有属性和方法 , 包括不可枚举的 , 不会去找原型对象
function Person(){
    Person.prototype.name = "oswald";
    Person.prototype.age = 24;
}
var person1 = new Person();
person1.color = "red";
var p1keys = Object.getOwnPropertyNames(person1);
console.log(p1keys);
// color
var Ppkeys = Object.getOwnPropertyNames(Person.prototype);
console.log(Ppkeys);
// constructor、name、age

原型模式创建对象的缺点

原型中的所有属性和方法都是共享的 , 如果有多个实例 , 通过其中一个实例改变原型对象中的属性和方法 , 其他实例访问的属性会跟着改变

组合使用构造函数模式和原型模式

将对象的私有属性和方法通过构造函数模式创建 , 将对象的公共属性和方法通过原型模式创建

function Person(name,age){
    /* 私有属性 */
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function(){
    /* 公共方法 */
    console.log(this.name);
}
var person1 = new Person("oswald", 24);
person1.sayName();  // oswald

继承

什么是原型链

原型链就是连接实例对象和原型对象的链接

/* 一般函数的原型链 */
function func(){
    console.log(123);
}

console.log(func.__proto__);    // Function.prototype
// func 函数是 Function 构造函数的实例对象
console.log(func.__proto__.__proto__);  // Object.prototype
// func 函数的原型对象是 Object 构造函数的实例对象
console.log(func.__proto__.__proto__.proto__);  // null
// 这里就是原型链的头了 , 所有原型链查到 Object.prototype 再往上就会返回 null

/* 一般构造函数的原型链 */
function Obj(){
    Func.prototype.name = "oswald";
}
var obj = new Obj();
console.log(obj);    // {}
console.log(obj.name);    // oswald
// obj 对象通过原型链查找到了 name 属性
console.log(Obj.__proto__);    // Obj.prototype
// obj 对象是 Obj 构造函数的实例对象
console.log(Obj.__proto__.__proto__);    // Object.prototype
// obj 对象的原型对象是 Object 构造函数的实例
console.log(Obj.__proto__.__proto__.__proto__);    // null
// 到头了

原型链继承

原型链继承的原理就是实例对象可以访问原型对象的属性和方法 , 并通过原型链向上查找

function Oswald(){
    this.color = "red";
    Oswald.prototype.sayName = function(){
        console.log(this.name)
    };
}
var oswald = new Oswald();

function Yin(name, age){
    this.name = name;
    this.age = age;
    Yin.prototype.sayAge = function(){
        console.log(this.age);
    }
}
Yin.prototype = oswald;
/*
原型链继承的核心 , 把父类型 ( Oswald ) 的实例对象 ( oswald )
设置为子类型 ( Yin ) 的原型属性 ( Yin.prototype )
*/

var yin = new Yin("oswald", 24);
// 必须要先继承再创建实例 , 否则会出现 Yin.prototype != yin.__proto__ 的情况

yin.sayName();    // oswald  继承了 Oswald 类型原型上的方法
yin.sayAge();     // 24      继承了 Yin 类型原型上的方法

借用构造函数式继承

  • 我们之前讨论的原型链继承 , 不能在创建子类型的实例对象的时候 , 给父类型的构造函数传递参数 , 如果要给父类型的构造函数传递参数 , 就会影响所有的子类型实例对象
  • 如果我们想要解决这个问题 , 可以借调父类型的构造函数 , 在新创建的对象上执行构造函数
function Oswald(color){
    this.color = color;
    this.sayName = function(){
        console.log(this.name)
    };
}

function Yin(name, age, color){
    Oswald.call(this, color);
    // 在 Yin 构造函数创建的新对象中调用 Oswald 函数
    this.name = name;
    this.age = age;
}

var yin = new Yin("yin", 24, "red");

yin.sayName();      // yin

组合式继承

但是借用构造函数继承只能够继承父类型自己的属性和方法 , 不能继承原型链上 , 这个时候我们可以使用原型链和借用构造函数的组合式继承 , 但是这个方法会调用两次父类型构造函数

function Super(color){
    this.color = color;
    // 自己的属性
    Super.prototype.sayName = function(){
        console.log(this.name)
    };
    // 原型链上的方法
}

function Sub(name, age, color){
    Super.call(this, color);        // 第二次调用 Super , 被当做普通函数调用
    // 继承 Super 构造函数自己的属性和方法
    this.name = name;
    this.age = age;
}
Sub.prototype = new Super();        // 第一次调用 Super , 被当做构造函数调用
// 继承 Super 原型链上的属性和方法
var yin = new Sub("yin", 24, "red");

yin.sayName();      // yin

原型式继承

ES 5 中使用 Object.create( o ) 方法规范了原型式继承 , 这个方法会返回一个新对象 , 新对象的原型对象指向传入的参数对象 o

function obj(o){    // Object.create( ) 方法的原理
    function F(){};
    F.prototype = o;
    return new F();
}
var person = {
    name: "oswald",
    color: ["red"]
}

var person1 = obj(person);
var person2 = Object.create(person);

console.log(person1 === person2);   // false
console.log(person1.__proto__ === person2.__proto__);   // true

寄生组合式继承

目前最优的继承模式

function Super(color){
    this.color = color;
    Super.prototype.sayName = function(){
        console.log(this.name);
    }
}
function Sub(name, color){
    Super.call(this, color);
    // 借调 Super 构造函数继承实例属性
    this.name = name;
}

var F = Object.create(Super.prototype);
F.constructor = Sub;
Sub.prototype = F;


var yin = new Sub("oswald","red");

yin.sayName();      // oswald

Oswald
1 声望0 粉丝

前端实习生