javascript构造函数及原型对象

转载于:https://www.cnblogs.com/alantao/p/5955911.html

/*
* @ javascript中没有类的概念,所以基在对象创建方面与面向对象语言有所不同
* @ 对象创建的常用方法及各自的局限性
* @ 使用Object或对象字面量创建对象
* @ 工厂模式创建对象
* @ 构造函数模式创建对象
* @ 原型模式创建对象
* @ 构造与原型混合模式创建对象
*/

使用Object创建对象

/*
* @ 使用Object或对象字面量创建对象
* @ 最基本的创建对象的方法
*/
// 创建一个student对象, new一个Object
var student = new Object();
student.name = "easy";
student.age = "20";
// 如果嫌这种方法麻烦, 有种封装不良的感觉,试试字面量创建

对象字面量方式来创建对象

/*
* @ 对象字面量方式来创建对象
* @ 缺点,无法重复
*/
var student = {
    name: "easy",
    age: "20"
}

// 当我们要创建同类的student1, student2,...,studentN时, 不得不将以上的代码重复N次
var student2 = {
    name: "easy2",
    age: "20"
}
// ...
var studentn = {
    name: "easyn",
    age: "20"
}
// 这样麻烦而且重复太多, 能不能像工厂间那样, 有一个车床不断生产出对象.

工厂模式创建对象

/*
* @ 工厂模式创建对象
* @ JS中没有类的概念, 那么我们不妨就使用一种函数将以上对象创建过程封装起来以便于重复调用
* @ 同时可以给出特定接口来初始化对象
*/
function createStudent(name, age){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    return obj;
}

var student1 = createStudent("easy1", 20);
var student2 = createStudent("easy2", 20);
// ...
var studentn = createStudent("easyn", 20);

// 这样就可以通过createStudent函数源源不断地"生产"对象了, 看起来很完美了,但我们不仅希望"产品"的生产可以像工厂间一样源源不断,还想知道生产的产品究竟是哪一种类型

// 比如说: 我们同时又定义了"生产"水果对象的createFruit()函数
function createFruit(name, color){
    var obj = new Object();
    obj.name = name;
    obj.color = color;
    return obj;
}

var v1 = createStudent("easy1", 20);
var v2 = createFruit("apple", "green");

console.log(v1 instanceof createStudent);   // false
console.log(v2 instanceof createFruit);     // false
console.log(v1 instanceof Object);          // true
console.log(v2 instanceof Object);          // true
// 如果我们用instanceof操作符去检测, 他们统统都是Object类型

// 我们希望v1是student类型, 而v2是fruit类型, 那我们可以用构造函数来创建对象

/*
* @ "构造函数" 和 "普通函数" 有什么区别
* @ 一.实际上并不存在创建构造函数的特殊语法, 构造函数就是一个普通函数, 与普通函数唯一的区别在于调用方法
* @ 二.对于任意函数,使用new操作符调用, 那么它就是构造函数; 不使用new操作符调用, 那么它就是普通函数
* @ 三.按照惯例,我们约定构造函数名以大写字母开头
* @ 四.使用new操作符调用构造函数时,会经历4个阶段
*   @ 1.创建一个新对象
*   @ 2.将构造函数作用赋给新对象(使用tihs指向该新对象)
*   @ 3.执行构造函数代码
*   @ 4.返回新对象
*/

构造函数模式创建对象

// 将工厂模式函数重写, 并添加一个方法属性
function Student(name, age){
    this.name = name;
    this.age = age;
    this.alertName = function(){
        alert(this.name);
    }
}

function Fruit(name, color){
    this.name = name;
    this.color = color;
    this.alertName = function(){
        alert(this.name);
    }
}

// 分别new出Student和Fruit的对象
var v1 = new Student("easy", 20);
var v2 = new Fruit("apple", "green");

// 这样就可以用instanceof操作符来检测以上对象类型区分出不同的对象
console.log(v1 instanceof Student);        // true
console.log(v2 instanceof Student);        // false
console.log(v1 instanceof Fruit);          // false
console.log(v2 instanceof Fruit);          // true
console.log(v1 instanceof Object);         // true
console.log(v2 instanceof Object);         // true

// 这样解决了工厂模式无法区分对象类型, 但这样就完美了吗, 在JS中,函数是对象, 当我们实例化不只一个Strudent对象的时候

var v1 = new Student("easy1", 20);
var v2 = new Student("easy2", 20);
//...
var vn = new Student("easyn", 20);
// 其中共同的alertName()函数也被实例化了n次, 我们可以用以下方法来检测不同的Strudent对象并不共用alertName()函数
console.log(v1.alertName == v2.alertName);      // false

// 这无疑是一种内存的浪费,我们知道this对象是在运行时基于函数的执行环境进行绑定. 在全局函数中,this对象等同于window, 在对象方法中,this指向该对象

// 试将对象方法移到构造函数外部
function Student(name, age){
    this.name = name;
    this.age = age;
    this.alertName = alertName;
}
function alertName(){
    alert(this.name);
}
var stu1 = new Student("easy1", 20);
var stu2 = new Student("easy2", 20);

// 在调用"stu1.alert()时", this对象才被绑定到stu1上, 通过alertName()函数定义为全局函数, 这样对象中的alertName属性则被设置为指向该全局的指针, 由此stu1和stu2共享了该全局函数, 解决了内在浪费的问题

// 但是,通过全局函数的方式解决对象内部共享的问题, 终究不像一个好的解决方法, 更好的方案是通过原型对象模式来解决

原型模式创建对象

/*
* 原型模式创建对象
* @ 函数的原型对象
* @ 对象实例和原型对象的关联
* @ 使用原型模型创建对象
* @ 原型模型创建对象的局限性
*/

/*
* 函数的原型对象
* @ 每一个函数都有一个prototype属性, 该属性是一个指针,该指针指向了一个对象
* @ 创建的构造函数, 该对象中包含可以由所有实例共享的属性和方法
* @ 在默认情况下,所有原型对象会自动包含一个constructor属性, 该属性也是一个指针, 指向prototype所在的函数
*/

/*
* 对象实例和原型对象的关联
* @ 在调用构造函数创建新的实例时, 该实例的内部会自动包含一个[[Prototype]]属性, 该指针指向构造函数的原型对象, 指针关联的是"实例与构造函数的原型对象" 而不是"实例与构造函数"
*/

使用原型模型创建对象

/*
* 使用原型模型创建对象
* @ 直接在原型对象中添加属性和方法
*/
function Student(){

}

Student.prototype.name = "easy";
Student.prototype.age = 20;
Student.prototype.alertName = function(){
    alert(this.name);
}

var stu1 = new Student();
var stu2 = new Student();
// true 二者共享同一个函数
console.log(stu1.alertName == stu2.alertName);

// 我们在Strudent的prototype对象添加了name, age属性以及alertName方法, 但创建的stu1和stu2中并不包含name, age属性以及alertName()方法, 只包含一个[[prototype]]指针属性, 当我们调用stu1.name或stu1.alertName时, 是如何找到对应的属性和方法的呢?

// 当我们需要读取对象的某个属性时, 都会执行一次搜索,首先在该对象中查找该属性, 若找到,返回该属性, 否则, 到[[prototype]] 指向的原型对象中继续查找

// 看出另一层意思是: 如果对象实例中包含和原型对象中同名的属性或方法, 则对象实例中的该同名属性或方法会屏蔽原型对象中的同名属性或方法, 原因就是"首先在该对象中查找该属性, 若找到,返回该属性值"

通过对象字面量重写原型对象

function Student(){}

Student.prototype = {
    constructor: Student,
    name: "easy",
    age: 20,
    alertName: function(){
        alert(this.name);
    }
}
// 要特别注意,我们这里相当于用对象字面量重新创建了一个Object对象,然后使Student的prototype指针指向该对象。该对象在创建的过程中,自动获得了新的constructor属性,该属性指向Object的构造函数。因此,我们在以上代码中,增加了constructor : Student使其重新指回Student构造函数。

// 原型模型创建对象的局限性
// 原型模型在对象实例共享数据方面给我们带来了很大的便利, 但通常情况下不同的实例会希望拥有属性自己的单独的属性, 我们将构造函数的模型和原型模型结合使用即可兼得数据共享和"不共享"

构造与原型混合模式创建对象

/*
* 构造与原型混合模式创建对象
* 我们结合原型模式在共享方法属性以及构造函数模式在实例方法属性方面的优势,使用以下的方法创建对象
* 在构造函数中定义实例属性,在原型中定义共享属性的模式,是目前使用最广泛的方式。通常情况下,我们都会默认使用这种方式来定义引用类型变量。
*/

// 我们希望每个stu拥有属于自己的name和age属性
function Student(name, age){
    this.name = name;
    this.age = age;
}

// 所有的stu应该共享一个alertName()方法
Student.prototype = {
    constructor: Student,
    alertName: function(){
        alert(this.name);
    }
}

var stu1 = new Student("Jim", 20);
var stu2 = new Student("Tom", 21);

stu1.alertName();
stu2.alertName();

alert(stu1.alertName == stu2.alertName);  //true  共享函数

猜你喜欢

转载自blog.csdn.net/github_39598787/article/details/84837533
今日推荐