网站建设上传文件,常州网站开发培训价格,永康住房城乡建设局网站,wordpress如何网站顶部右侧广告[js] 说说你对js对象生命周期的理解
一切皆对象
咱们经常听到JS中“一切皆对象”#xff1f;有没有问想过这是什么意思#xff1f;其它语言也有“一切皆对象”之说#xff0c;如Python。但是Python中的对象不仅仅是像JS对象这样的存放值和值的容器。Python中的对象是一个类…[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_fmtpng
谁把这些方法放在函数呢。 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这种方式比刚开始会点还可以进一步优化就是使用prototypeprototype是一个对象可以在上面扩展属性方法等等。
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__指向构造函数的 prototypeobj.__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: TrueTom也是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
个人简介
我是歌谣欢迎和大家一起交流前后端知识。放弃很容易 但坚持一定很酷。欢迎大家一起讨论
主目录
与歌谣一起通关前端面试题