资讯专栏INFORMATION COLUMN

理解js对象

zhouzhou / 1450人阅读

摘要:将构造函数的作用域赋值给新对象因此指向了新对象执行构造函数的代码为这个新对象添加属性返回对象最初是用来标识对象类型的。但提到检测对象类型,还是使用将构造函数当作函数构造函数与其他函数唯一区别。

创建对象

虽然Object构造函数与对象字面量都能创建单个对象, 但这些方式都有明显的缺点: 使用同一个接口创建很多对象, 会产生大量重复代码。

var obj = {}; //对象字面量
var obj = new Object(); //对象构造函数(对象构造器)
工厂模式

这种模式抽象了对象具体创建的过程(类似其它语言的类)

function createPerson (name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function () {
      alert (this.name);
    }
    return o;
}
var person1 = createPerson("汪淼", 50, "纳米");
var person2 = createPerson("杨冬", 20, "基础物理学");

工厂模式解决了创建多个对象的问题, 确没有解决对象识别问题(如何知道一个对象的类型)因为使用该模式并没有给出对象的类型

构造函数模式

创建自定义构造函数意味着将来可以将它的实列类型标识为一种特定的类型。(更优点)
这种方式定义的函数是定义在global中的

function Person (name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function () {
      alert (this.name);
    }
}
var person1 = new Person("汪淼", 50, "纳米");
var person2 = new Person("杨冬", 20, "基础物理学");

与工厂模式区别

没有显示的创建对象

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

没有return语句

使用new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

创建(或者说构造)一个全新的对象。

这个新对象会被执行[[原型]]连接。

这个新对象会绑定到函数调用的this 。

如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。

高级编程对象处的说法
1.创建(或者说构造)一个全新的对象。
2.将构造函数的作用域赋值给新对象(因此this指向了新对象)
3.执行构造函数的代码(为这个新对象添加属性)
4.返回对象
person1.constructor == Person;
person2.constructor == Person;

constructor 最初是用来标识对象类型的。但提到检测对象类型,还是使用 instanceof

person1 instanceof Person // true
person2 instanceof Person // true
将构造函数当作函数

构造函数与其他函数唯一区别。调用方式的不同。 用new 操作符调用就是作为构造函数,不用则为普通函数

   // 构造函数
   var person = new Person("罗辑", 20, "宇宙社会学");
   person.sayName(); //罗辑
   // 普通函数
   Person("叶文洁", 20, "基础物理学");
   window.sayName(); //叶文洁
   // 在另一个对象作用域中调用
   var o = new Object();
   Person.call(o, "泰勒", 25, "面壁者"); //call() apply() 是会立即调用函数的 而bind() 则不会
   o.sayName(); // 泰勒
构造函数问题

使用构造函数的主要问题是每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = new Function("alert(this.name)"); 
  // 等价于
  // this.sayName = function () {
  //    alert (this.name);
  //  }
}
差的尝试
  function Person (name, age, job) {
      this.name = name;
      this.age = age;
      this.job = job;
      this.sayName = sayName;
  }
  function sayName() {
    alert (this.name);
  }
  var person1 = new Person("汪淼", 50, "纳米");
  var person2 = new Person("杨冬", 20, "基础物理");

sayName 放到了构造函数外部, 构造函数内部通过sayName指针指向sayName函数;
问题: 创建了全局函数sayName

于是这个自定义引用类型就没有丝毫封装性可言

原型模式 原型模式

我们创建的每个函数都有一个 prototype(原型属性),该属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型所有实例共享的属性和方法。

用原型对象,可以让所有实例共享它的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中

function Person() {

}
Person.prototype.name = "维德";
Person.prototype.age = "24";
Person.prototype.sayName = function() {
  alert(this.name);
}

var person1 = new Person();
person1.sayName(); // 维德
var person2 = new Person();
person2.sayName(); // 维德
alert(person1.sayName == person2.sayName); //true
// @todo 原型链图 
更简单的原型模式
function Person(){};
Person.prototype = {
    name: "丁怡",
    age: 50,
    job: "物流",
    sayName : function(){
        console.log(this.name);
    }
};
var person1 = new Person();
person1.sayName();// "丁怡"
console.log(person1.constructor === Person);// false
console.log(person1.constructor === Object);// true

这种语法 constructor 不再指向Person,(每创建一个函数,就会同时创建prototype, 同时这个对象也会自动获取constructor),而该方法本质上相当于完全重写了prototype的默认属性,constructor 也变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。此时尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了

var friend = new Person();
friend instanceof Object; // true
friend instanceof Person; // true
friend constructor Person; // false
friend constructor Object; // true
修正 constructor 方法
function Person(){};
Person.prototype = {
    // 方法1:添加 constructor
    constructor: "Person",
    name: "丁怡",
    age: 50,
    job: "物流",
    sayName : function(){
        console.log(this.name);
    }
};
// 方法2:通过defineProperty() 添加
Object.defineProperty(Person.prototype,"constructor",{
    enumerable: false,
    value: Person
});
原型 1.理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获取constructor(构造函数)属性,这个属性包含了一个指向prototype属性所在函数数的指针

2.原型与 in 操作符

in 会查找 原型链 hasOwnProperty() 则只查找实例本身

3.更简单的原型模式
function Person(){};
Person.prototype = {
    name: "丁怡",
    age: 50,
    job: "物流",
    sayName : function(){
        console.log(this.name);
    }
};
var person1 = new Person();
person1.sayName();// "丁怡"
console.log(person1.constructor === Person);// false
console.log(person1.constructor === Object);// true
4.原型的动态性 && 重写原型
var friend = new Person();
Person.prototype.sayHi = function() {
  alert("hi");
}
friend.sayHi(); // hi (ok!)

重写function原型之后

function Person() {}
var friend = new Person();
Person.prototype = {
  constructor: Person,
  name: "程心",
  age: 21,
  job: "天体物理",
  sayName: fucntion() {
    alert(this.name);
  }
};
friend.sayName(); //error  原型链重写 @todo 原型链图
5.原生对象的原型

js 所有原生的引用类型,都是采用这种模式(Object, Array, String)

原型对象的问题

原型模式问题在于引用类型值属性会被所有的实例对象共享并修改,这也是很少有人多带带使用原型模式的原因

function Person(){}
Person.prototype = {
    constructor: Person,
    name: "bai",
    age: 29,
    job: "Software Engineer",
    friend : ["shelby","Court"],
    sayName: function(){
        console.log(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//["shelby","Court","Van"];
alert(person2.friends);//["shelby","Court","Van"];
alert(person1.friends === person2.friends);//true
组合使用构造函数模式和原型模式

组合使用构造函数模式和原型模式是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数。实例对象都有自己的一份实例属性的副本,同时又共享对方法的引用,最大限度地节省了内存。该模式是目前使用最广泛、认同度最高的一种创建自定义对象的模式

  function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["丁怡", "汪淼"];
  }
  Person.prototype = {
    constructor: Person,
    sayName: function() {
      alert(this.name);
    }
  }
var person1 = new Person("bai",29,"Software Engineer");
var person2 = new Person("hu",25,"Software Engineer");
person1.friends.push("杨冬");
alert(person1.friends);// ["丁怡", "汪淼", "杨冬"];
alert(person2.friends);// ["丁怡", "汪淼"];
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true
动态原型模式

动态原型模式将组合模式中分开使用的构造函数和原型对象都封装到了构造函数中,然后通过检查方法是否被创建,来决定是否初始化原型对象

function Person(name, age, job) {
  // 属性
  this.name = name;
  this.age = age;
  this.job = job;
  // 方法
  if(typeof this.sayName != "function") {
    Person.prototype.sayName = function(){
        console.log(this.name);
    };
  }
}
var friend = new Person("bai",29,"Software Engineer");
friend.sayName();//"bai"
寄生构造函数模式

该模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。该模式是工厂模式和构造函数模式的结合

  寄生构造函数模式与构造函数模式有相同的问题,每个方法都要在每个实例上重新创建一遍,创建多个完成相同任务的方法完全没有必要,浪费内存空间

function Person(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    };
    // o.sayName = new Function("console.log(this.name);");
    return o;
}
var person1 = new Person("bai", 29, "software Engineer");
var person2 = new Person("hu", 25, "software Engineer");
//具有相同作用的sayName()方法在person1和person2这两个实例中却占用了不同的内存空间
console.log(person1.sayName === person2.sayName);//false

使用new 操作符会默认返回新对象实例,这里return 重写了调用构造函数的时返回的值

稳妥构造函数模式

所谓稳妥对象指没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new)或者在防止数据被其他应用程序改动时使用

  稳妥构造函数与寄生构造函数模式相似,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数

function Person(name,age,job){
    //创建要返回的对象
    var o = new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName = function(){
        console.log(name);
    };
    //返回对象
    return o;
}
//在稳妥模式创建的对象中,除了使用sayName()方法之外,没有其他方法访问name的值
var friend = Person("bai",29,"Software Engineer");
friend.sayName();//"bai"

与寄生构造函数模式相似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有什么意义

最后

本文从使用Object构造函数与对象字面量创建一个对象开始说起,创建多个对象会造成代码冗余;使用工厂模式可以解决该问题,但存在对象识别的问题;接着介绍了构造函数模式,该模式解决了对象识别的问题,但存在关于方法的重复创建问题;接着介绍了原型模式,该模式的特点就在于共享,但引出了引用类型值属性会被所有的实例对象共享并修改的问题;最后,提出了构造函数和原型组合模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数,该模式是目前使用最广泛的一种模式。此外,一些模式下面还有一些解决特殊需求的拓展模式(寄生构造函数模式, 稳妥寄生模式)

Object构造函数与对象字面量创建一个对象 创建多个对象会造成代码冗余

使用工厂模式可以解决该问题 存在对象识别的问题

介绍了构造函数模式,该模式解决了对象识别的问题 但存在关于方法的重复创建问题

介绍了原型模式,该模式的特点就在于共享 但引出了引用类型值属性会被所有的实例对象共享并修改的问题

提出了构造函数和原型组合模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,这种组合模式还支持向构造函数传递参数,该模式是目前使用最广泛的一种模式

一些模式下面还有一些解决特殊需求的拓展模式(寄生构造函数模式, 稳妥寄生模式)

参考

javascript面向对象系列第二篇——创建对象的5种模式

javascript高级编程

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88929.html

相关文章

  • javascript引擎执行的过程的理解--语法分析和预编译阶段

    摘要:所以觉得把这个执行的详细过程整理一下,帮助更好的理解。类似的语法报错的如下图所示三预编译阶段代码块通过语法分析阶段之后,语法都正确的下回进入预编译阶段。另开出新文章详细分析,主要介绍执行阶段中的同步任务执行和异步任务执行机制事件循环。 一、概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的。看了很多这方便文章,大多数是讲的是事件循环(event loo...

    molyzzx 评论0 收藏0
  • javascript系列--javascript引擎执行的过程的理解--语法分析和预编译阶段

    摘要:所以觉得把这个执行的详细过程整理一下,帮助更好的理解。类似的语法报错的如下图所示三预编译阶段代码块通过语法分析阶段之后,语法都正确的下回进入预编译阶段。另开出新文章详细分析,主要介绍执行阶段中的同步任务执行和异步任务执行机制事件循环。 一、概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的。看了很多这方便文章,大多数是讲的是事件循环(event loo...

    malakashi 评论0 收藏0
  • [ 学习路线 ] 学完这些去阿里!GOGOGO

    摘要:以下知识点是前辈师兄总结基础语义化标签引进了一些新的标签,特别注意等,注意的标题结构理解浏览器解析的过程,理解的树形结构,及相应理解标签在各个浏览器上的默认样式代理样式,理解中的重置样式表的概念理解等功能性标签理解标签,理解文件提交过程推荐 以下知识点是前辈师兄总结 1、HTML/HTML5基础: 1.0、语义化H5标签1.1、H5引进了一些新的标签,特别注意article...

    zhaochunqi 评论0 收藏0
  • [ 学习路线 ] 学完这些去阿里!GOGOGO

    摘要:以下知识点是前辈师兄总结基础语义化标签引进了一些新的标签,特别注意等,注意的标题结构理解浏览器解析的过程,理解的树形结构,及相应理解标签在各个浏览器上的默认样式代理样式,理解中的重置样式表的概念理解等功能性标签理解标签,理解文件提交过程推荐 以下知识点是前辈师兄总结 1、HTML/HTML5基础: 1.0、语义化H5标签1.1、H5引进了一些新的标签,特别注意article...

    learn_shifeng 评论0 收藏0
  • 形象化模拟作用域链,深入理解js作用域、闭包

    摘要:至此作用域链创建完毕。好了,通过深入理解作用域链,我们能跟好的理解的运行机制和闭包的原理。 前言 理解javascript中的作用域和作用域链对我们理解js这们语言。这次想深入的聊下关于js执行的内部机制,主要讨论下,作用域,作用域链,闭包的概念。为了更好的理解这些东西,我模拟了当一个函数执行时,js引擎做了哪些事情--那些我们看不见的动作。 关键词: 执行环境 作用域 作用域链 变...

    txgcwm 评论0 收藏0
  • JS每日一题:如何理解es6中的Proxy?

    20190124问: 如何理解es6中的Proxy? 试题解析:对proxy的理解,可能会延伸到vue的双向绑定 Proxy(代理) 定义 可以理解为为目标对象架设一层拦截,外界对该对象的访问,都必须通过这层拦截 简单示例: const obj = new Proxy({}, { get: (target, key, receiver) => { return JS ...

    tinysun1234 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<