前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【前端】:对象、原型、继承

【前端】:对象、原型、继承

作者头像
WEBJ2EE
发布2020-02-26 09:48:28
1K0
发布2020-02-26 09:48:28
举报
文章被收录于专栏:WebJ2EEWebJ2EE
代码语言:javascript
复制
目录
1. 对象
    1.1. 对象描述符
        1.1.1. 数据描述符
        1.1.2. 存取描述符
    1.2. 不变性
        1.2.1. 常量属性
        1.2.2. 禁止扩展
        1.2.3. 密封
        1.2.4. 冻结
    1.3. 属性访问[[Get]]
    1.4. 属性赋值[[Set]]
2. 原型 [[Prototype]]
    2.1. Object.prototype
    2.3. prototype、[[Prototype]]、__proto__
    2.3. .constructor 属性不可靠
    2.4. 运算符 new
    2.5. .constructor 属性不可靠
    2.6. instanceof 的本质是什么?
3. 模拟类式继承的常见方法
    3.1. 原型链继承
    3.2. 借用构造函数
    3.3. 组合继承(原型链继承+借用构造函数)
    3.4. 共享原型
    3.5. 临时构造函数   
4. 几道笔试题

1. 对象

1.1. 属性描述符

在 ES5 之前,JavaScript 语言本身并没有提供可以直接检测属性特性的方法,比如判断属性是否是只读。但是从 ES 开始,所有的属性都具备了属性描述符。

对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一,不能同时是两者

图1-1 数据描述符

图1-2 存取描述符

1.1.1. 数据描述符

  • writable:当 writable 属性设置为 false 时,该属性被称为“不可写”。它不能被重新分配。

图1-3 writable=false示例,非strict模式

图1-4 writable=false示例,strict模式

图1-5 configurable=false,仍可以转换writable为false

  • enumerable:enumerable定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

图1-6 enumerable=false 示例

图1-7 writeable 出现在原型上,会怎么样

  • configurable:configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。

图1-8 configurable=false 示例

1.1.2. 存取描述符

总结:

1.2. 不变性

有时候你会希望属性或者对象是不可改变的,在 ES5 中可以通过很多种方法来实现。

1.2.1. 常量属性(不能改动属性)

结合 configuration:false 和 writable:false就可以创建一个真正的常量属性(不可修改、重定义或者删除)。

1.2.2. 禁止扩展(不能新增属性)

如果一个对象可以添加新的属性,则这个对象是可扩展的。Object.preventExtensions()将对象标记为不再可扩展,因此它将永远不会具有超出它被标记为不可扩展的属性。注意,一般来说,不可扩展对象的属性可能仍然可被删除。

1.2.3. 密封

Object.seal(...) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(...) 并把所有现有属性标记为 configurable: false。所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)。

代码语言:javascript
复制
var obj = {
  prop: function() {},
  foo: 'bar'
};

// New properties may be added, existing properties
// may be changed or removed.
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;

var o = Object.seal(obj);

o === obj; // true
Object.isSealed(obj); // === true

// Changing property values on a sealed object
// still works.
obj.foo = 'quux';

// But you can't convert data properties to accessors,
// or vice versa.
Object.defineProperty(obj, 'foo', {
  get: function() { return 'g'; }
}); // throws a TypeError

// Now any changes, other than to property values,
// will fail.
obj.quaxxor = 'the friendly duck';
// silently doesn't add the property
delete obj.foo;
// silently doesn't delete the property

// ...and in strict mode such attempts
// will throw TypeErrors.
function fail() {
  'use strict';
  delete obj.foo; // throws a TypeError
  obj.sparky = 'arf'; // throws a TypeError
}
fail();

// Attempted additions through
// Object.defineProperty will also throw.
Object.defineProperty(obj, 'ohai', {
  value: 17
}); // throws a TypeError
Object.defineProperty(obj, 'foo', {
  value: 'eit'
}); // changes existing property value

1.2.4. 冻结

Object.freeze(....) 会创建一个冻结对象,这个方法会在一个现有对象上调用 Object.seal(...) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们值。

代码语言:javascript
复制
var obj = {
  prop: function() {},
  foo: 'bar'
};

// 新的属性会被添加, 已存在的属性可能
// 会被修改或移除
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;

// 作为参数传递的对象与返回的对象都被冻结
// 所以不必保存返回的对象(因为两个对象全等)
var o = Object.freeze(obj);

o === obj; // true
Object.isFrozen(obj); // === true

// 现在任何改变都会失效
obj.foo = 'quux'; // 静默地不做任何事
// 静默地不添加此属性
obj.quaxxor = 'the friendly duck';

// 在严格模式,如此行为将抛出 TypeErrors
function fail(){
  'use strict';
  obj.foo = 'sparky'; // throws a TypeError
  delete obj.quaxxor; // 返回true,因为quaxxor属性从来未被添加
  obj.sparky = 'arf'; // throws a TypeError
}

fail();

// 试图通过 Object.defineProperty 更改属性
// 下面两个语句都会抛出 TypeError.
Object.defineProperty(obj, 'ohai', { value: 17 });
Object.defineProperty(obj, 'foo', { value: 'eit' });

// 也不能更改原型
// 下面两个语句都会抛出 TypeError.
Object.setPrototypeOf(obj, { x: 20 })
obj.__proto__ = { x: 20 }

1.3. 属性访问[[Get]]

1.4. 属性赋值[[Set]]

2. 原型 [[Prototype]]

JavaScript 中的对象都有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。

2.1. Object.prototype

[[Prototype]]的尽头是哪里?

  • 所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。
  • Object.prototype 的 [[Prototype]] 为 null。

2.2. 属性设置和屏蔽

别小看这一句代码,很多套路

代码语言:javascript
复制
myObject.foo = "bar";

再祭出这张神图

  • 如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值。
  • 如果在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性并且没有被标记为只读(writable:false),那么会直接在 myObject 中添加一个名为 foo 的新属性。
  • 如果在 [[Prototype]] 链上层存在 foo,但是它被标记为只读 (writable: false),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。
  • 如果在 [[Prototype]] 链上层存在 foo并且它是一个 setter,那就一定会调用这个 setter。foo 不会被添加到 myObject,也不会重新定义 foo 这个 setter。

2.3. prototype、[[Prototype]]、__proto__

  • 每个对象内部都有 [[Prototype]] 内置属性,指向它的原型对象。
  • __proto__ 属性是一个访问器属性(一个getter函数和一个setter函数), 暴露了通过它访问的对象的内部[[Prototype]]。
  • 每个函数都有一个名为 prototype 的属性,它指向原型对象。;

2.4. 运算符 new

图4:演示构造函数与 new

实际上,NothingSpecial 和你程序中的其他函数没有任何区别。函数本身并不是构造函数,然而,当你在普通的函数调用前面加上 new 关键字之后,就会把这个函数调用变成一个“构造函数调用”。实际上,new 会劫持所有普通函数并用构造对象的形式来调用它。

——《你不知道的 JavaScript (上卷)》p150

换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用

——《你不知道的 JavaScript (上卷)》p150

2.5. .constructor 属性不可靠

.constructor 从哪来?

.constructor 为啥不可靠?

Car.prototype 的 .constructor 属性只是 Car 函数在声明时的默认属性。如果你创建了一个新对象并替换了函数默认的 .prototype 对象引用,那么新对象并不会自动获得 .constructor 属性

.constructor 并不是一个不可变属性。它是不可枚举的,但是它的值是可写的。你可以任意对其赋值。所以 .constructor 是一个非常不可靠并且不安全的引用。

.constructor 该如何利用?

虽然 .constructor 属性不可靠也不安全,但是它可以很方便的用于运行时对象的内省。可以重置 .constructor 属性使其指向期望的构造函数而不会影响其功能,这是由于该属性主要是用于提供对象的信息。

——《JavaScript 模式》

2.6. instanceof 的本质是什么?

  • JavaScript 中,instanceof 操作符的左操作数是一个普通的对象,右操作数是一个函数。instanceof 回答的问题是:在 john 的整条 [[Prototype]] 链中是否有 Employee.prototype 指向的对象?
  • ES6 的 .isPrototypeOf 接口还原了 instanceof 操作符的本质含义;

示例1:

示例2:

3. 模拟类式继承的常见方法

3.1. 原型链继承

3.2. 借用构造函数

3.3. 组合继承(原型链继承+借用构造函数)

3.4. 共享原型

3.5. 临时构造函数

4. 几道笔试题

题目01:

题目02:

参考:

《你不知道的 JavaScript (上卷)》 Object.defineProperty(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty Object prototypes: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes new 运算符: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new ECMA-262-10th: http://www.ecma-international.org/ecma-262/10.0/index.html#sec-get-o-p http://www.ecma-international.org/ecma-262/10.0/index.html#sec-getv http://www.ecma-international.org/ecma-262/10.0/index.html#sec-set-o-p-v-throw http://www.ecma-international.org/ecma-262/10.0/index.html#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档