JS 对象生命周期的秘密 java对象生命周期和类生命周期
bigegpt 2024-10-19 02:52 14 浏览
我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。
一切皆对象
咱们经常听到JS中“一切皆对象”?有没有问想过这是什么意思?其它语言也有“一切皆对象”之说,如Python。但是Python中的对象不仅仅是像JS对象这样的存放值和值的容器。Python中的对象是一个类。JS中有类似的东西,但JS中的“对象”只是键和值的容器:
var obj = { name: "Tom", age: 34 }
实际上,JS中的对象是一种“哑”类型,但很多其他实体似乎都是从对象派生出来的。甚至是数组,在JS中创建一个数组,如下所示:
var arr = [1,2,3,4,5]
然后用typeof运算符检查类型,会看到一个令人惊讶的结果:
typeof arr "object"
看来数组是一种特殊的对象!即使JS中的函数也是对象。如果你深入挖掘,还有更多,创建一个函数,该函数就会附加一些方法:
var a = function(){ return false; } a.toString()
输出:
"function(){ return false; }"
咱们并没有在函数声明toString方法,所以在底层一定还有东西。它从何而来?Object有一个名为.toString的方法。似乎咱们的函数具有相同的Object方法。
Object.toString()
这时咱们使用浏览器控制台来查看默认被附加的函数和属性,这个谜团就会变得更加复杂:
谁把这些方法放在函数呢。 JS中的函数是一种特殊的对象,这会不会是个暗示? 再看看上面的图片:我们的函数中有一个名为prototype的奇怪命名属性,这又是什么鬼?
JS中的prototype是一个对象。它就像一个背包,附着在大多数JS内置对象上。例如 Object, Function, Array, Date, Error,都有一个“prototype”:
typeof Object.prototype // 'object' typeof Date.prototype // 'object' typeof String.prototype // 'object' typeof Number.prototype // 'object' typeof Array.prototype // 'object' typeof Error.prototype // 'object'
注意内置对象有大写字母:
- String
- Number
- Boolean
- Object
- Symbol
- Null
- Undefined
以下除了Object是类型之外,其它是JS的基本类型。另一方面,内置对象就像JS类型的镜像,也用作函数。例如,可以使用String作为函数将数字转换为字符串:
String(34)
现在回到“prototype”。prototype是所有公共方法和属性的宿主,从祖先派生的“子”对象可以从使用祖先的方法和属性。也就是说,给定一个原始 prototype,咱们可以创建新的对象,这些对象将使用一个原型作为公共函数的真实源,不 Look see see。
假设有个要求创建一个聊天应用程序,有个人物对象。这个人物可以发送消息,登录时,会收到一个问候。
根据需求咱们很容易定义这个么一 Person 对象:
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } };
你可能会想知道,为什么这里要使用字面量的方式来声明 Person 对象。稍后会详细说明,现在该 Person 为“模型”。通过这个模型,咱们使用 Object.create() 来创建以为这个模型为基础的对象。
创建和链接对象
JS中对象似乎以某种方式链接在一起,Object.create()说明了这一点,此方法从原始对象开始创建新对象,再来创建一个新Person 对象:
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } }; var Tom = Object.create(Person);
现在,Tom 是一个新的对象,但是咱们没有指定任何新的方法或属性,但它仍然可以访问Person中的name和age 属性。
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } }; var Tom = Object.create(Person); var tomAge = Tom.age; var tomName = Tom.name; console.log(`${tomAge} ${tomName}`); // Output: 0 noname
现在,可以从一个共同的祖先开始创建新的person。但奇怪的是,新对象仍然与原始对象保持连接,这不是一个大问题,因为“子”对象可以自定义属性和方法
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } }; var Tom = Object.create(Person); Tom.age = 34; Tom.name = "Tom"; var tomAge = Tom.age; var tomName = Tom.name; console.log(`${tomAge} ${tomName}`); // Output: 34 Tom
这种方式被称为“屏蔽”原始属性。还有另一种将属性传递给新对象的方法。Object.create将另一个对象作为第二个参数,可以在其中为新对象指定键和值:
var Tom = Object.create(Person, { age: { value: 34 }, name: { value: "Tom" } });
以这种方式配置的属性默认情况下不可写,不可枚举,不可配置。不可写意味着之后无法更改该属性,更改会被忽略:
var Tom = Object.create(Person, { age: { value: 34 }, name: { value: "Tom" } }); Tom.age = 80; Tom.name = "evilchange"; var tomAge = Tom.age; var tomName = Tom.name; Tom.greet(); console.log(`${tomAge} ${tomName}`); // Hello Tom // 34 Tom
不可枚举意味着属性不会在 for...in 循环中显示,例如:
for (const key in Tom) { console.log(key); } // Output: greet
但是正如咱们所看到的,由于JS引擎沿着原型链向上查找,在“父”对象上找到greet属性。最后,不可配置意味着属性既不能修改也不能删除。
Tom.age = 80; Tom.name = "evilchange"; delete Tom.name; var tomAge = Tom.age; var tomName = Tom.name; console.log(`${tomAge} ${tomName}`); // 34 Tom
如果要更改属性的行为,只需配writable(可写性),configurable(可配置),enumerable(可枚举)属性即可。
var Tom = Object.create(Person, { age: { value: 34, enumerable: true, writable: true, configurable: true }, name: { value: "Tom", enumerable: true, writable: true, configurable: true } });
现在,Tom也可以通过以下方式访问greet():
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } }; var Tom = Object.create(Person); Tom.age = 34; Tom.name = "Tom"; var tomAge = Tom.age; var tomName = Tom.name; Tom.greet(); console.log(`${tomAge} ${tomName}`); // Hello Tom // 34 Tom
暂时不要过于担心“this”。拉下来会详细介绍。暂且先记住,“this”是对函数执行的某个对象的引用。在咱们的例子中,greet() 在Tom的上下文中运行,因此可以访问“this.name”。
构建JavaScript对象
目前为止,只介绍了关于“prototype”的一点知识 ,还有玩了一会 Object.create()之外但咱们没有直接使用它。随着时间的推移出现了一个新的模式:构造函数。使用函数创建新对象听起来很合理, 假设你想将Person对象转换为函数,你可以用以下方式:
function Person(name, age) { var newPerson = {}; newPerson.age = age; newPerson.name = name; newPerson.greet = function() { console.log("Hello " + newPerson.name); }; return newPerson; }
因此,不需要到处调用object.create(),只需将Person作为函数调用:
var me = Person("Valentino");
构造函数模式有助于封装一系列JS对象的创建和配置。在这里, 咱们使用字面量的方式创建对象。这是一种从面向对象语言借用的约定,其中类名开头要大写。
上面的例子有一个严重的问题:每次咱们创建一个新对象时,一遍又一遍地重复创建greet()函数。可以使用Object.create(),它会在对象之间创建链接,创建次数只有一次。首先,咱们将greet()方法移到外面的一个对象上。然后,可以使用Object.create()将新对象链接到该公共对象:
var personMethods = { greet: function() { console.log("Hello " + this.name); } }; function Person(name, age) { // greet lives outside now var newPerson = Object.create(personMethods); newPerson.age = age; newPerson.name = name; return newPerson; } var me = Person("Valentino"); me.greet(); // Output: "Hello Valentino"
这种方式比刚开始会点,还可以进一步优化就是使用prototype,prototype是一个对象,可以在上面扩展属性,方法等等。
Person.prototype.greet = function() { console.log("Hello " + this.name); };
移除了personMethods。调整Object.create的参数,否则新对象不会自动链接到共同的祖先:
function Person(name, age) { // greet lives outside now var newPerson = Object.create(Person.prototype); newPerson.age = age; newPerson.name = name; return newPerson; } Person.prototype.greet = function() { console.log("Hello " + this.name); }; var me = Person("Valentino"); me.greet(); // Output: "Hello Valentino"
现在公共方法的来源是Person.prototype。使用JS中的new运算符,可以消除Person中的所有噪声,并且只需要为this分配参数。
下面代码:
function Person(name, age) { // greet lives outside now var newPerson = Object.create(Person.prototype); newPerson.age = age; newPerson.name = name; return newPerson; }
改成:
function Person(name, age) { this.name = name; this.age = age; }
完整代码:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.greet = function() { console.log("Hello " + this.name); }; var me = new Person("Valentino"); me.greet(); // Output: "Hello Valentino"
注意,使用new关键字,被称为“构造函数调用”,new 干了三件事情
- 创建一个空对象
- 将空对象的proto指向构造函数的prototype
- 使用空对象作为上下文的调用构造函数
- function Person(name, age) {
- this.name = name;
- this.age = age;
- }
根据上面描述的,new Person("Valentino") 做了:
- 创建一个空对象:var obj = {}
- 将空对象的proto__`指向构造函数的 prototype:`obj.__proto = Person().prototype
- 使用空对象作为上下文调用构造函数:Person.call(obj)
检查原型链
检查JS对象之间的原型链接有很多种方法。例如,Object.getPrototypeOf是一个返回任何给定对象原型的方法。考虑以下代码:
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } }; var Tom = Object.create(Person);
检查Person是否是Tom的原型:
var tomPrototype = Object.getPrototypeOf(Tom); console.log(tomPrototype === Person); // Output: true
当然,如果使用构造函数调用构造对象,Object.getPrototypeOf也可以工作。但是应该检查原型对象,而不是构造函数本身:
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.greet = function() { console.log("Hello " + this.name); }; var me = new Person("Valentino"); var mePrototype = Object.getPrototypeOf(me); console.log(mePrototype === Person.prototype); // Output: true
除了Object.getPrototypeOf之外,还有另一个方法isPrototypeOf。该方法用于测试一个对象是否存在于另一个对象的原型链上,如下所示,检查 me 是否在 Person.prototype 上:
Person.prototype.isPrototypeOf(me) && console.log('Yes I am!')
instanceof运算符也可以用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。老实说,这个名字有点误导,因为JS中没有“实例”。在真正的面向对象语言中,实例是从类创建的新对象。请考虑Python中的示例。咱们有一个名为Person的类,咱们从该类创建一个名为“tom”的新实例:
class Person(): def __init__(self, age, name): self.age = age; self.name = name; def __str__(self): return f'{self.name}' tom = Person(34, 'Tom')
注意,在Python中没有new关键字。现在,咱们可以使用isinstance方法检查tom是否是Person的实例
isinstance(tom, Person) // Output: True
Tom也是Python中“object”的一个实例,下面的代码也返回true:
isinstance(tom, object) // Output: True
根据isinstance文档,“如果对象参数是类参数的实例,或者是它的(直接、间接或虚拟)子类的实例,则返回true”。咱们在这里讨论的是类。现在让咱们看看instanceof做了什么。咱们将从JS中的Person函数开始创建tom(因为没有真正的类)
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.greet = function() { console.log(`Hello ${this.name}`); }; var tom = new Person(34, "Tom");
使用isinstance方法检查tom是否是Person和 Object 的实例
if (tom instanceof Object) { console.log("Yes I am!"); } if (tom instanceof Person) { console.log("Yes I am!"); }
因此,可以得出结论:JS对象的原型总是连接到直接的“父对象”和Object.prototype。没有像Python或Java这样的类。JS是由对象组成,那么什么是原型链呢?如果你注意的话,咱们提到过几次“原型链”。JS对象可以访问代码中其他地方定义的方法,这看起来很神奇。再次考虑下面的例子:
var Person = { name: "noname", age: 0, greet: function() { console.log(`Hello ${this.name}`); } }; var Tom = Object.create(Person); Tom.greet();
即使该方法不直接存在于“Tom”对象上,Tom也可以访问greet()。
这是JS的一个内在特征,它从另一种称为Self的语言中借用了原型系统。当访问greet()时,JS引擎会检查该方法是否可直接在Tom上使用。如果不是,搜索将继续向上链接,直到找到该方法。
“链”是Tom连接的原型对象的层次结构。在我们的例子中,Tom是Person类型的对象,因此Tom的原型连接到Person.prototype。而Person.prototype是Object类型的对象,因此共享相同的Object.prototype原型。如果在Person.prototype上没有greet(),则搜索将继续向上链接,直到到达Object.prototype。这就是咱们所说的“原型链”。
保护对象不受操纵
大多数情况下,JS 对象“可扩展”是必要的,这样咱们可以向对象添加新属性。但有些情况下,我们希望对象不受进一步操纵。考虑一个简单的对象:
var superImportantObject = { property1: "some string", property2: "some other string" };
默认情况下,每个人都可以向该对象添加新属性
var superImportantObject = { property1: "some string", property2: "some other string" }; superImportantObject.anotherProperty = "Hei!"; console.log(superImportantObject.anotherProperty); // Hei!
Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。
var superImportantObject = { property1: "some string", property2: "some other string" }; Object.preventExtensions(superImportantObject); superImportantObject.anotherProperty = "Hei!"; console.log(superImportantObject.anotherProperty); // undefined
这种技术对于“保护”代码中的关键对象非常方便。JS 中还有许多预先创建的对象,它们都是为扩展而关闭的,从而阻止开发人员在这些对象上添加新属性。这就是“重要”对象的情况,比如XMLHttpRequest的响应。浏览器供应商禁止在响应对象上添加新属性
var request = new XMLHttpRequest(); request.open("GET", "https://jsonplaceholder.typicode.com/posts"); request.send(); request.onload = function() { this.response.arbitraryProp = "我是新添加的属性"; console.log(this.response.arbitraryProp); // undefined };
这是通过在“response”对象上内部调用Object.preventExtensions来完成的。您还可以使用Object.isExtensible方法检查对象是否受到保护。如果对象是可扩展的,它将返回true:
var superImportantObject = { property1: "some string", property2: "some other string" }; Object.isExtensible(superImportantObject) && console.log("我是可扩展的");
如果对象不可扩展的,它将返回false:
var superImportantObject = { property1: "some string", property2: "some other string" }; Object.preventExtensions(superImportantObject); Object.isExtensible(superImportantObject) || console.log("我是不可扩展的!");
当然,对象的现有属性可以更改甚至删除
var superImportantObject = { property1: "some string", property2: "some other string" }; Object.preventExtensions(superImportantObject); delete superImportantObject.property1; superImportantObject.property2 = "yeees"; console.log(superImportantObject); // { property2: 'yeees' }
现在,为了防止这种操作,可以将每个属性定义为不可写和不可配置。为此,有一个方法叫Object.defineProperties。
var superImportantObject = {}; Object.defineProperties(superImportantObject, { property1: { configurable: false, writable: false, enumerable: true, value: "some string" }, property2: { configurable: false, writable: false, enumerable: true, value: "some other string" } });
或者,更方便的是,可以在原始对象上使用Object.freeze:
var superImportantObject = { property1: "some string", property2: "some other string" }; Object.freeze(superImportantObject);
Object.freeze工作方式与Object.preventExtensions相同,并且它使所有对象的属性不可写且不可配置。唯一的缺点是“Object.freeze”仅适用于对象的第一级:嵌套对象不受操作的影响。
class
有大量关于ES6 类的文章,所以在这里只讨论几点。JS是一种真正的面向对象语言吗?看起来是这样的,如果咱们看看这段代码
class Person { constructor(name) { this.name = name; } greet() { console.log(`Hello ${this.name}`); } }
语法与Python等其他编程语言中的类非常相似:
class Person: def __init__(self, name): self.name = name def greet(self): return 'Hello' + self.name
或 PHP
class Person { public $name; public function __construct($name){ $this->name = $name; } public function greet(){ echo 'Hello ' . $this->name; } }
ES6中引入了类。但是在这一点上,咱们应该清楚JS中没有“真正的”类。一切都只是一个对象,尽管有关键字class,“原型系统”仍然存在。新的JS版本是向后兼容的,这意味着在现有功能的基础上添加了新功能,这些新功能中的大多数都是遗留代码的语法糖。
总结
JS中的几乎所有东西都是一个对象。从字面上看。JS对象是键和值的容器,也可能包含函数。Object是JS中的基本构建块:因此可以从共同的祖先开始创建其他自定义对象。然后咱们可以通过语言的内在特征将对象链接在一起:原型系统。
从公共对象开始,可以创建共享原始“父”的相同属性和方法的其他对象。但是它的工作方式不是通过将方法和属性复制到每个孩子,就像OOP语言那样。在JS中,每个派生对象都保持与父对象的连接。使用Object.create或使用所谓的构造函数创建新的自定义对象。与new关键字配对,构造函数类似于模仿传统的OOP类。
思考
- 如何创建不可变的 JS 对象?
- 什么是构造函数调用?
- 什么是构造函数?
- “prototype” 是什么?
- 可以描述一下 new 在底层下做了哪些事吗?
作者:valentinogagliardi
译者:前端小智
来源:github
相关推荐
- 得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践
-
一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...
- warm-flow新春版:网关直连和流程图重构
-
本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...
- 扣子空间体验报告
-
在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...
- spider-flow:开源的可视化方式定义爬虫方案
-
spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...
- solon-flow 你好世界!
-
solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...
- 新一代开源爬虫平台:SpiderFlow
-
SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...
- 通过 SQL 训练机器学习模型的引擎
-
关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...
- 鼠须管输入法rime for Mac
-
鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...
- Go语言 1.20 版本正式发布:新版详细介绍
-
Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...
- iOS 10平台SpriteKit新特性之Tile Maps(上)
-
简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...
- 程序员简历例句—范例Java、Python、C++模板
-
个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...
- Telerik UI for iOS Q3 2015正式发布
-
近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...
- ios使用ijkplayer+nginx进行视频直播
-
上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...
- IOS技术分享|iOS快速生成开发文档(一)
-
前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...
- macOS下配置VS Code C++开发环境
-
本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- httperror403.14-forbidden (63)
- logstashinput (65)
- hadoop端口 (65)
- dockernetworkconnect (63)
- esxi7 (63)
- vue阻止冒泡 (67)
- c#for循环 (63)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- java大写转小写 (63)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)