JavaScript 通过创建对象来理解对象

网上关于创建对象的博文已经烂大街了,写的比我好的也已经烂大街了,那我为什么还要写这篇博文呢,因为我发现有些东西写下来、敲出来才是真的理解了。至少这篇文章对我来说意义重大。

搜了下网上的创建对象的几种方法:有条回答获赞数比较高而且十分简洁:熟读 《JavaScript 高级程序设计》第六章。

简单粗暴,我就直接去翻尘封已久的犀牛书。

本文参考《JavaScript 高级程序设计(第三版)》,大部分代码 Copy,Copy 是抄写,不是复制粘贴;部分文字带有自己的理解。

先总结下读完这一章后引出的,我认为比较重要的知识点:

  • 对象的属性类型:数据属性和访问器属性
  • new 方法做了什么
  • 基本数据类型和引用数据类型
  • 栈内存与堆内存

——————————————————————————————————————————————————

正文:

面向对象的语言都有一个标志,即类。

上帝根据自己的形象造男造女,这里的上帝便是男男女女便是对象

在对象中,每一个属性和方法都已一个名字,而每个名字都映射到一个值,即 ECMAScript 中的对象无非就是一组名值对,其中值可以是数据或者函数。

创建对象的方法

  1. 最简单粗暴:对象字面量
var person = new Object();
persion.name = 'Bob';
persion.age = 24;
persion.job = 'Software Engineer'
persion.sayName = function(){
    consolo.log(this.name)
}
var person = {
    name: 'Bob',
    age: 24,
    job: 'Software Engineer',
    sayName: function(){
        consolo.log(this.name)
    }
}

  1. 工厂模式
function createDevice(name,userId,job){
    var obj = new Object();
    obj.name = name;
    obj.userId = userId;
    obj.job = job
    obj.sayName = function(){
        console.log(this.name)
        return this.name
    }
    return obj
}
var device1 = createDevice('device1',1,'light')
console.log(device1) // { name: 'device1', userId: 1, job: 'light', sayName: [Function] }
console.log(device1 instanceof Object) // true
  1. 构造函数模式
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
}; }
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1) // Person {name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: [Function] }
console.log(person1 instanceof Object) // true,

创建 Person 实例,必须使用new 操作符,这种方式实际上经历了以下四个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

缺点: 每个方法都要在每个实例上重新创建一遍,所有实例内部的 Function 都不是一个同一个 Function,(注: 因为在 JS 中,一切皆对象,嗯,联想到了函数声明,每定义一个函数也就是实例化一个对象,逻辑角度是等价的):

console.log(person1.sayName === person2.sayName) // false
  1. 原型模式
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.sayName();   //"Nicholas"
var person2 = new Person();
console.log(person1.sayName === person2.sayName) // true

这样就不必在构造函数中定义对象实例的信息,而是将这些信息直接添加到原型对象中

在这里是图片描述

无论什么时候:

  1. 只要创建了函数,就会根据函数特定的规划为函数创建一个 prototype 属性,这个属性指向函数的原型对象
  2. 原型对象自动获得一个 constructor 属性,这个属性包含一个指向 prototype 属性所在函数的指针

但是,为什么说这样的方式可以避免出现像构造函数那样的问题呢?

原来是这样:在调用 person1.sayName() 的时候,解析器首先会问:

  1. “实例 person1sayName 属性吗?” 答:“没有。"
  2. 然后,它继续搜索,再问:“person1 的原型有 sayName 属性吗? ”答:“有。”
  3. 于是,它就读取那个保存在原型对象中的函数。当我们调用 person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。而这正是多个对象实例共享原型所保存的属性和方法的基本原理

JS 对象的理解好像又深刻了,我又尝试以下代码:

console.log(Person.prototype) 
// Person {name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: [Function] }
console.log(person1.prototype) 
// undefined
console.log(person1.constructor.prototype) 
// Person {name: 'Nicholas',age: 29,job: 'Software Engineer',sayName: [Function] }

所以:我以前对Person.prototype 的理解是:它是函数的原型对象,现在看来,这种理解是不够准确的。

其实:

  • prototype 只是每个函数在被创建时自带的一个属性,这个属性指向函数的原型对象,

  • 从而可以使用person1.constructor 获取原型对象中的 constructor 属性,

  • construtor 指向的是 Person

  • 所以 person1.constructor.prototype 也不难理解

(好吧,刚开始真的有点绕,多读几次其实还好)

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性;换句话说,添加这
个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性,举例如下

person1.name = "Greg";
alert(person1.name); // "Greg"——来自实例
alert(person2.name); // "Nicholas"——来自原型

delete person1.name;
alert(person1.name); // "Nicholas"——来自原型

// 注意理解是屏蔽,我的理解也就是优先级是与作用域保持一致,但是只是屏蔽,并没有改变原型中属性的的值。

在这里插入图片描述

有个方法可以判断一个属性是存在于实例中,还是存在于原型中。

person1.name = "Greg";
alert(person1.name); // "Greg"——来自实例 
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); // "Nicholas"——来自原型 
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); // "Nicholas"——来自原型 
alert(person1.hasOwnProperty("name")); //false
  1. 原型模式进阶
function Person(){}
Person.prototype = {
    name : " Nicholas ",
    age : 29,
    friends : ["Shelby", "Court"],
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

原型模式的缺点:

如果原型对象上面的属性所对应的值是引用类型,那么问题就来了。众所周知,对于 JS的数据类型分为两类:**基本类型 **和 引用类型,我所理解的他们的区别主要是存储的空间不同:

  • 基本数据类型存在于栈内存,键值对的方式存储
  • 对于引用数据类型,栈内存中存的是键和引用地址,而引用地址指向的是堆内存中该对象所存储的地方

所以:

person1.friends.push("Ayden");
alert(person1.friends);    //"Shelby,Court,Ayden"
alert(person2.friends);    //"Shelby,Court,Ayden"
alert(person1.friends === person2.friends);  //true

因为它们的引用地址指向的是同一个堆内存对象(数组),所以,每个实例一般都是有专属于自己的属性的。

书中是这样说的:

假如我们的初衷就是像这样:
在所有实例中共享一个数组,那么对这个结果我没有话可说。可是,实例一般都是要有属于自己的全部
属性的。而这个问题正是我们很少看到有人单独使用原型模式的原因所在。

  1. 组合使用构造函数模式和原型模式(最广泛)
function Person(name, age, job){
this.name = name; 3 this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
 2
  }
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Ayden");
alert(person1.friends);    // "Shelby,Count,Ayden"
alert(person2.friends);    // "Shelby,Count"
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true


实例所有的属性都用构造函数定义,所有的方法以及 constructor 属性都早原型对象中定义。

都这样做的好处就是确保每个实例的属性都是自己独立的,但是共享了对方法的引用,最大限度的节省了内存空间。

  1. 动态原型模式
function Person(name, age, job){
	//属性
    this.name = name; 
    this.age = age; 
    this.job = job;
    //方法
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            alert(this.name);
    	}; 
    }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

然后突然有个问题困惑了,直接上代码:

// 代码 1
function Person(name, age, job){
    this.name = name; 
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
    Person.prototype = {
    	constructor : Person,
    	sayName : function(){
        	alert(this.name);
    	}
	} 
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1.sayName === person2.sayName) // false


// 代码 2
function Person(name, age, job){
    this.name = name; 
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
} 
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
console.log(person1.sayName === person2.sayName) // true

论封装性,代码一不是更好吗?为什么都是 prototype 属性的重写,代码一和代码二的结果却不一样?

我想到了 new 关键字做了什么:创建对象,指针指向,执行函数,返回对象,我应该是忽略了执行函数这一步。

代码一中,每次实例化对象,prototype 属性都进行了重写,重写了两次,所以改变了现有实例与新原型之间的联系。

而代码二中,虽然同样是两次实例化,但不同的是全局代码也就是给 Person 的原型定义只执行了一次,所以 sayName 方法的引用指向的是同一个堆内存对象。

书中是这样写的:

使用动态原型模式时,不能使用对象字面量重写原型。前面已经解释过了,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。

我犯得就是这个错误。

还有两种创建对象的模式,寄生构造函数模式和稳妥构造函数模式,我工作中还没接触过,只看了看原理,没有去深入理解。

理解以下这些知识点对理解创建对象比较有帮助:

  • 对象的属性类型:数据属性和访问器属性
  • new 方法做了什么
  • 基本数据类型和引用数据类型
  • 栈内存与堆内存

以上是我在读第 6 章第一节和第二节后的笔记以及收获,有什么不对的地方还麻烦大家提出,互相讨论,感激。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值