[js] 说说你对js对象生命周期的理解

一切皆对象

咱们经常听到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()

这时咱们使用浏览器控制台来查看默认被附加的函数和属性,这个谜团就会变得更加复杂:

640?wx_fmt=png

谁把这些方法放在函数呢。 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'

注意内置对象有大写字母:

StringNumberBooleanObjectSymbolNullUndefined

以下除了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 nowvar 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 nowvar 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 nowvar 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) {

根据上面描述的,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 = namedef 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 在底层下做了哪些事吗?

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

原文:https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter5.md

个人简介

我是歌谣,欢迎和大家一起交流前后端知识。放弃很容易,
但坚持一定很酷。欢迎大家一起讨论

主目录

与歌谣一起通关前端面试题

[js] 说说你对js对象生命周期的理解相关推荐

  1. 【转】【iOS知识学习】_视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear等的区别及用途...

    原文网址:http://blog.csdn.net/weasleyqi/article/details/8090373 iOS视图控制对象生命周期-init.viewDidLoad.viewWillA ...

  2. 容器,对象生命周期管理的基石

    2019独角兽企业重金招聘Python工程师标准>>> 郑重申明:包括本文在内的很多技术文章,大多出自山外高人,而非Fans. Fans暂时没有能力写作优秀的技术文章,Fans只是转 ...

  3. 《Imperfect C++中文版》——2.1 对象生命周期

    本节书摘来自异步社区出版社<Imperfect C++中文版>一书中的第2章,第2.1节,作者: [美]Matthew Wilson,更多章节内容可以访问云栖社区"异步社区&qu ...

  4. iOS视图控制对象生命周期-init、viewDidLoad、viewWillAppear、v...

    2019独角兽企业重金招聘Python工程师标准>>> iOS视图控制对象生命周期-init.viewDidLoad.viewWillAppear.viewDidAppear.vie ...

  5. Spring.NET学习笔记(5)-对象生命周期和创建者对象

    一.对象生命周期 说白了就是一init初始化方法和Dispose方法 两种实现方式 1.实现接口方法(造成耦合,放弃),IInitializingObject / init-method和IDispo ...

  6. iOS视图控制对象生命周期-init、viewDidLoad、viewWillAppear、viewDidAppear、viewWillDisappear、view...

    iOS视图控制对象生命周期: init.viewDidLoad.viewWillAppear.viewDidAppear.viewWillDisappear.viewDidDisappear的区别及用 ...

  7. java对象生命周期_Java对象生命周期和类生命周期

    原标题:Java对象生命周期和类生命周期 作者:彭空空 链接:https://www.jianshu.com/p/25ea857ba78b 导读 对象的生命周期 类的加载机制 类的生命周期 类加载器 ...

  8. SSH学习-Hibernate对象生命周期管理

    Hibernate对象就是java中的实体对象,管理就是在实体对象的生命周期内被Hibernate的操作,Hibernate对象的生命周期其实就是实体对象的生命周期(从创建到最后被GC回收),期间对实 ...

  9. Swift之深入解析Xcode13对Swift对象生命周期的优化

    在 Xcode13 中,在 Build Setting 中,新增 Optimize Object Lifetimes 编译选项,默认是关闭的,Apple 建议将该选项设置为 YES,打开此优化项,可以 ...

最新文章

  1. Intellij Idea 导入多个maven项目展示在左侧栏Maven Projects
  2. Nginx读取Memcached实现页面内容缓存
  3. 使用 Nexus3镜像搭设私有仓库(Bower 、Docker、Maven、npm、NuGet、Yum、PyPI)
  4. Flutter RichText 使用案例解析 Flutter WidgetSpan 设置图片显示
  5. python类中self关键字用法
  6. PHP学习记录(一)
  7. 推荐一款数据恢复软件(迅龙数据恢复)
  8. Eclipse启动问题:An error is occurred
  9. 2020幻影围棋 第三天围棋规则模块(一)
  10. Microsoft Excel 教程:如何在 Excel 中筛选区域或表中的数据?
  11. HttpClient完整使用示例
  12. Java岗面试:java技术面试问题
  13. 教育期刊《中学语文教学参考》杂志简介及投稿须知
  14. OSChina 娱乐弹弹弹——凉风有信,秋月无边
  15. 大学生计算机学科竞赛a类,专业学科竞赛(A类)
  16. python 拼音汉字和识字,python-pinyin
  17. 基于 ANFIS 的非线性回归(Matlab代码实现)
  18. python录制鼠标动作_使用Python w / django - 我可以录制用户输入(键盘和鼠标)并播放它吗?...
  19. 又一个Mac特洛伊木马被发现!苹果用户要警惕
  20. 【自制】csdn自定义模块栏目 个性化 【美化个人简介】

热门文章

  1. [转]FFMPEG调节音频的音量大小,混音
  2. Centos7完全分布式搭建Hadoop2.7.3
  3. ECharts学习(1)--简单图表的绘制
  4. android报错及解决1--Bitmap加载时,报bitmap size exceeds VM budget
  5. Oracle 时区(TimeZone )-- DST
  6. python增删改查csv文件_Python--作业2--对员工信息文件,实现增删改查操作
  7. 计算机语言乍么设置,电脑如何设置语言
  8. centos redis验证_centos7中安装、配置、验证、卸载redis
  9. C++ 面向对象(四)—— 多态 (Polymorphism)
  10. [react] 在React中如何判断点击元素属于哪一个组件?