前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《你不知道的JavaScript(上)之原型对象》读书笔记

《你不知道的JavaScript(上)之原型对象》读书笔记

原创
作者头像
after the rain
发布2022-08-08 16:11:51
6280
发布2022-08-08 16:11:51
举报
文章被收录于专栏:《你不知道的JS》读书笔记

1.语法

  • 对象的文字语法:
代码语言:javascript
复制
let myObj = { 
     key1: 'value1'
     key2: 'value2'
 }
  • 构造函数形式
代码语言:javascript
复制
let  myObj = new Object();
myObj.key = value

两种不同的创建模式有什么区别呢?

new的工作原理:

1.创建一个空对象,构造函数中的this会指向这个对象

2.这个新对象会被链接到原型

3.执行构造函数方法,其属性和方法都会被添加到this引用的对象中

4.如果构造函数中没有返回新对象,那么返回this,即创建新对象;否则,返回构造函数中返回的对象。

new和字面量创建对象的区别: 1.字面量创建对象,不会调用Object构造函数,简洁且性能更好;

2.new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。

2.类型

  • 在 JavaScript 中一共有六种主要类型:string、boolean、number、null、undefined、object
  • 注意,简单基本类型(string、boolean、number、null、undefined)本身并不是对象。
  • null 有时会被当作一种对象类型,但是这其实只是语言本身的一个 bug,即对 null 执行 typeof null 时会返回字符串 "object"。实际上,null 本身是基本类型。
  • 有一种常见的错误说法是“JavaScript 中万物皆是对象”,这显然是错误的。
  • 实际上,JavaScript 中有许多特殊的对象子类型,我们可以称之为复杂基本类型。
  • 函数就是对象的一个子类型(从技术角度来说就是“可调用的对象”)。JavaScript 中的函数是“一等公民”,因为它们本质上和普通的对象一样(只是可以调用),所以可以像操作其他对象一样操作函数(比如当作另一个函数的参数)。
  • 数组也是对象的一种类型,具备一些额外的行为。数组中内容的组织方式比一般的对象要 稍微复杂一些。

内置对象

  • JavaScript 中还有一些对象子类型,通常被称为内置对象。有些内置对象的名字看起来和 简单基础类型一样,不过实际上它们的关系更复杂。
  • 内置对象:String、Number、Boolean、Object、Function、Array、Data、RegExp、Error 在 JavaScript 中,它们实际上是一些内置函数。这些内置函数可以当作构造函数 (由 new 产生的函数调用)来使用,从而可以构造一个对应子类型的新对象。 举例来说: let strPrimitive = "I am a string"; typeof strPrimitive; // "string" strPrimitive instanceof String; // false var strObject = new String( "I am a string" ); typeof strObject; // "object" strObject instanceof String; // true // 检查 sub-type 对象 Object.prototype.toString.call( strObject ); // [object String]

原始值 "I am a string" 并不是一个对象,它只是一个字面量,并且是一个不可变的值。 如果要在这个字面量上执行一些操作,比如获取长度、访问其中某个字符等,那需要将其转换为 String 对象。 幸好,在必要时语言会自动把字符串字面量转换成一个 String 对象,也就是说你并不需要显式创建一个对象。 例如:

代码语言:javascript
复制
let strPrimitive = "I am a string"; 
console.log( strPrimitive.length ); // 13 
console.log( strPrimitive.charAt( 3 ) ); // "m"

使用以上两种方法,我们都可以直接在字符串字面量上访问属性或者方法,之所以可以这 样做,是因为引擎自动把字面量转换成 String 对象,所以可以访问属性和方法。

3.内容(对象的属性)

  • 对象的内容是由一些存储在特定命名位置的(任意类型的)值组成的,我们称之为属性。
  • 需要强调的一点是,当我们说“内容”时,似乎在暗示这些值实际上被存储在对象内部, 但是这只是它的表现形式。在引擎内部,这些值的存储方式是多种多样的,一般并不会存在对象容器内部。存储在对象容器内部的是这些属性的名称,它们就像指针(从技术角度来说就是引用)一样,指向这些值真正的存储位置。
  • 在对象中,属性名永远都是字符串。如果你使用 string(字面量)以外的其他值作为属性 名,那它首先会被转换为一个字符串。即使是数字也不例外,虽然在数组下标中使用的 的确是数字,但是在对象属性名中数字会被转换成字符串,所以不要搞混对象和数组中数字的用法: let myObject = { }; myObject[true] = "foo"; myObject[3] = "bar"; myObject[myObject] = "baz"; myObject["true"]; // "foo" myObject["3"]; // "bar" myObject["[object Object]"]; // "baz"

1.对于字符串字面量(string)、数值字面量(number)、布尔字面量(boolean)来说,我们都可以直接在其上面访问属性或者方法,之所以可以这样做,是因为引擎自动把字面量转换成 String、Number、Boolean对象,所以可以访问属性和方法。 2.null、undefined 没有对应的构造形式,它们只有文字形式。相反,Date 只有构造(new Date(..)),没有文字形式。 3.对于 Object、Array、Function、RegExp来说,无论使用文字形式还是构造形式,它们都是对象,不是字面量。 4.Error 对象很少在代码中显式创建,一般是在抛出异常时被自动创建。也可以使用 new Error(..) 这种构造形式来创建,不过一般来说用不着。

可计算属性名

ES6 增加了可计算属性名,也可以叫做可拼接,因为字符串中的“+“、“*”运算符会被js引擎解析为拼接 可以在文字形式中使用 [] 包裹一个表达式来当作属性名:

代码语言:javascript
复制
let prefix = "foo";
let myObject = {
    [prefix + "bar"]:"hello", 
    [prefix + "baz"]: "world"
};
myObject["foobar"]; // hello
myObject["foobaz"]; // world

可计算属性名最常用的场景可能是 ES6 的符号(Symbol)。简单来说,Symbol是一种新的基础数据类型,包含一个不透明且无法预测的值(从技术角度来说就是一个字符串)。一般来说你不会用到符号的实际值(因为理论上来说在不 同的 JavaScript 引擎中值是不同的),所以通常你接触到的是符号的名称,比如 Symbol. Something

代码语言:javascript
复制
let myObject = {
    [Symbol.Something]: "hello world"
}
复制对象
  • Array:sliceconcatArray.from()
  • Object: Object.assign()JSON.parse(JSON.stringify(obj)) 不过使用JSON.parse(JSON.stringify(obj))的话,undefined任意的函数symbol在序列化过程中会被忽略(出现在非数组对象的属性中时)或者被转换成null(出现在数组中时)

在这里扩展一下关于浅拷贝与深拷贝:

浅拷贝:只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存。仅仅会针对第一层的数据进行处理,深层嵌套的数据不会进行处理。

深拷贝:创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。

浅拷贝的两种方法:

1.使用for in 遍历对象 挨个赋值

2.使用Object.assign()方法

代码语言:javascript
复制
let objA = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            }
        }
let objB = {}
         for(var k in objA) {
             // k 属性名  obj[k]属性值
             objB[k] = objA[k]
         }
        Object.assign(objB,objA)
        objB.msg.age = 20
        console.log(objB);
        console.log(objA);

Object.assign的源码:

代码语言:javascript
复制
var _extends = Object.assign || function (target) {
            for (var i = 1; i < arguments.length; i++) {   //arguments为全部的对象集合
                var source = arguments[i];                 //单个对象
                //遍历一个对象的自身和继承来的属性,
                //常常配合hasOwnProperty筛选出对象自身的属性
                for (var key in source) {
                    //使用call方法,避免原型对象扩展带来的干扰
                    if (Object.prototype.hasOwnProperty.call(source, key)) {   ///判断是否为source自身的属性。  (非继承)说明Object.assign只能加源对象的自身的属性,不算继承的。
                        target[key] = source[key];
                    }
                }
            }
            return target;
        };

从源码中我们不难看出,Object.assign方法不对深层对象进行拷贝,且不拷贝继承式的属性

代码语言:javascript
复制
// 用法:Object.assign(target, ...sources)
// target:目标对象,后续所有的对象合并都是基于target进行合并
// sources:源对象,可以是多个,会依次从右向左进行覆盖合并

const obj1 = {
  name: "nordon",
  info: {
    msg: 'msg',
    innerInfo: {
      msg: 'msg'
    }
  }
};

const obj2 = {
  age: 12,
  name: 'wy',
  info: {
    foo: 'bar'
  }
};

const newObj = Object.assign(obj1, obj2);
console.log(newObj);// 这里info属性相同,由于是浅拷贝,所以直接覆盖第一层 newObj.info = { foo:bar }
代码语言:javascript
复制
const obj = Object.create({
  age: 1 // age 是继承属性
});

const newObj = Object.assign(obj, {name: 'nordon'})
console.log(newObj) //  {name: 'nordon'}

浅拷贝的两种方法:

1.使用递归一层一层往外拷贝

2.使用序列化JSON.stringify(JSON.parse(Obj))方法

代码语言:javascript
复制
  obj = {
            id: 1,
            name: 'andy',
            data: [{
                id: 2,
                name: 'lihua'
            },{
                id: 3,
                name: 'xiaoming'
            }],
            msg: {
                age: 18
            }
        }
         o = {}
        function deepObj(newobj, oldobj) {
            for(k in oldobj) {
                //判断我们的属性值属于哪种数据类型
                //1.获取属性值
                var item = oldobj[k]
                if(item instanceof Array) {      //2.判断这个属性值是否是数组
                    newobj[k] = []
                    deepObj(newobj[k], item)
                } else if (item instanceof Object) {  //3.判断这个属性值是否是对象
                    newobj[k] = {}
                    deepObj(newobj[k], item)
                } else {        //4.属于简单数据类型                
                    newobj[k] = item
                }
 
            }
        }
        deepObj(o, obj)
let newObj = JSON.parse(JSON.stringify(obj));
console.log(o,newObj );
3.属性描述符

writable(可写)enumerable(可枚举)configurable(可配置)

代码语言:javascript
复制
let myObject = {      
    a:2 
}; 
Object.getOwnPropertyDescriptor( myObject, "a" );  
// { 
//    value: 2, 
//    writable: true, 决定是否可以修改属性的值。
//    enumerable: true, 决定这个描述符控制的是属性是否会出现在对象的属性枚举中。
//    configurable: true 决定是否可配置对象的值。
// }可以看到属性描述符的默认值

在创建普通属性时属性描述符会使用默认值,我们也可以使用 Object.defineProperty(..) 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。 举例来说:

代码语言:javascript
复制
let myObject = {}; 

Object.defineProperty( myObject, "a", {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true 
} );  
myObject.a; // 2
常量对象

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

代码语言:javascript
复制
let myObject = {};
Object.defineProperty( myObject, "FAVORITE_NUMBER", {
    value: 42,
    writable: false,
    configurable: false 
} );
禁止扩展

如果你想禁止一个对象添加新属性并且保留已有属性,可以使用 Object.prevent Extensions(..):

代码语言:javascript
复制
let myObject = { 
    a:2
};
Object.preventExtensions( myObject );
myObject.b = 3;
myObject.b; // undefined
密封

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

冻结

Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们的值。 这个方法是你可以应用在对象上的级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(不过就像我们之前说过的,这个对象引用的其他对象是不受影响的)。 你可以“深度冻结”一个对象,具体方法为,首先在这个对象上调用 Object.freeze(..), 然后遍历它引用的所有对象并在这些对象上调用 Object.freeze(..)。但是一定要小心,因为这样做有可能会在无意中冻结其他(共享)对象。

4.【Get】& 【put】

在JS语言规范中,myObject.amyObject 上实际上是实现了 [[Get]] 操作(有点像函数调 用:[[Get]]())。对象默认的内置 [[Get]] 操作首先在对象中查找是否有名称相同的属性, 如果找到就会返回这个属性的值。 然而,如果没有找到名称相同的属性,按照 [[Get]] 算法的定义会执行另外一种非常重要的行为遍历可能存在的 [[Prototype]] 链, 也就是原型链如果无论如何都没有找到名称相同的属性,那 [[Get]] 操作会返回值 undefined

[[put]] 被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素)。 如果已经存在这个属性,[[put]] 算法大致会检查下面这些内容。

  • 属性是否是访问描述符(参见3.3.9节)?如果是并且存在setter就调用setter。
  • 属性的数据描述符中writable是否是false?如果是,在非严格模式下静默失败,在严格模式下抛出 TypeError 异常。
  • 如果都不是,将该值设置为属性的值。
5.【Getter】& 【Setter】

对象默认的 [[Put]] 和 [[Get]] 操作分别可以控制属性值的设置和获取。 在 ES5 中可以使用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法 应用在整个对象上。getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏 函数,会在设置属性值时调用。 当你给一个属性定义 getter、setter 或者两者都有时,这个属性会被定义为“访问描述符”(和“数据描述符”相对)。对于访问描述符来说,JavaScript 会忽略它们的 value 和 writable 特性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性。

代码语言:javascript
复制
var myObject = {
    // 给 a 定义一个 getter 
    get a() {
        return 2; 
    }
};
Object.defineProperty( 
    myObject, // 目标对象 
    "b", // 属性名
    {   // 描述符
        // 给 b 设置一个 getter
        get: function(){ 
            return this.a * 2 
        },
        // 确保 b 会出现在对象的属性列表中
        enumerable: true
    }
);
myObject.a; // 2
myObject.b; // 4

无论是对象文字语法中的get a(){..},还是 defineProperty(..) 中的显式定义,二者都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值。 为了让属性更合理,还应当定义 setter,和你期望的一样,setter 会覆盖单个属性默认的 [put] 操作。通常来说 getter 和 setter 是成对出现的(只定义一个的话 通常会产生意料之外的行为):

代码语言:javascript
复制
var myObject = {
    // 给 a 定义一个 getter 
    get a() {
        return this._a_; 
    },
    // 给 a 定义一个 setter 
    set a(val) {
        this._a_ = val * 2; 
    }
};
myObject.a = 2;
myObject.a; // 4

遍历

  • for..in 循环可以用来遍历对象的可枚举属性列表(包括 [[Prototype]] 链)。
  • forEach(..)every(..)some(..)
  • for..of,循环每次调用 myObject 迭代器对象的 next() 方法时,内部的指针都会向前移动并 返回对象属性列表的下一个值。
代码语言:javascript
复制
var myArray = [ 1, 2, 3 ];
var it = myArray[Symbol.iterator]();   // 使用 ES6 中的符号 Symbol.iterator 来获取对象的 @@iterator 内部属 性。
it.next(); // { value:1, done:false } 
it.next(); // { value:2, done:false } 
it.next(); // { value:3, done:false } 
it.next(); // { done:true }

第4章 混合对象“类”

类、继承、实例化、多态

4.1.1 “类”设计模式
  • 面向对象设计模式,比如迭代器模式、观察者模式、工厂模式、单例模式,等等。
  • 最好使用类把过程化风格的“意大利面代码”转换成结构清晰、组织良好的代码。
4.2 类的机制
  • 一个类就是一张蓝图。为了获得真正可以交互的对象,我们必须按照类来建造(也可以说实例化)一个东西,这个东西通常被称为实例。这个对象就是类中描述的所有特性的一份副本。
4.3.1 多态

多态是说父类的通用行为可以被子类用更特殊的行为重写。 多态并不表示子类和父类有关联,子类得到的只是父类的一份副本。类的继承其实就是复制。

第5章 原型

5.1 [[Prototype]]
  • 几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。
  • 对于默认的 [[Get]] 操作来说,如果无法在对象本身找到需要的属性,就会继续访问对象的 [[Prototype]] 链。这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]] 链。如果是后者的话, [[Get]] 操作的返回值是 undefined
  • 使用 for..in 遍历对象时,使用 in 操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链
5.1.1 [[Prototype]] 的“尽头”

所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype,所以它包含 JavaScript 中许多通用的功能。

5.1.2 属性设置和屏蔽

如果 foo 不直接存在于 myObject 中而是存在于原型链上层时 myObject.foo = "bar" 会出现的三种情况

  • 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性(参见第3章)并且没 有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新 属性,它是屏蔽属性。
  • 如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable:false),那么 无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会 抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
  • 如果在[[Prototype]]链上层存在foo并且它是一个setter(参见第3章),那就一定会 调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这 个 setter。 有些情况下会隐式产生屏蔽,一定要当心。思考下面的代码:
代码语言:javascript
复制
var anotherObject = { 
    a:2
};

var myObject = Object.create( anotherObject );

anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false

myObject.a++; // 隐式屏蔽! 

anotherObject.a; // 2
myObject.a; // 3

myObject.hasOwnProperty( "a" ); // true

尽管 myObject.a++ 看起来应该(通过委托)查找并增加 anotherObject.a 属性,但是别忘了 ++ 操作相当于 myObject.a = myObject.a + 1。因此 ++ 操作首先会通过 [[Prototype]] 查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用 [[Put]] 将值 3 赋给 myObject 中新建的屏蔽属性 a,天呐! 修改委托属性时一定要小心。如果想让 anotherObject.a 的值增加,唯一的办法是 anotherObject.a++。

5.2 “类”

5.2.1 “类”函数

所有的函数默认都会拥有一个名为 prototype 的公有并且不可枚举的属性,它会指向另一个对象:

代码语言:javascript
复制
function Foo() { 
    // ...
}
Foo.prototype; // { }

var a = new Foo();
Object.getPrototypeOf( a ) === Foo.prototype; // true

new Foo() 只是间接完成我们的目标:一个关联到其他对象的新对象。

5.2.2 “构造函数”
代码语言:javascript
复制
function Foo() { 
    // ...
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
// a.constructor 只是通过默认的 [[Prototype]] 委托指向 Foo

Foo.prototype 默认有一个公有并且不可枚举的属性 .constructor,这个属性引用的是对象关联的函数(本例中是 Foo)。此外,我们可以看到通过“构造函数”调用 new Foo() 创建的对象也有一个 .constructor 属性,指向 “创建这个对象的函数”。 实际上 a 本身并没有 .constructor 属性。而且,虽然 a.constructor 确实指向 Foo 函数,但是这个属性并不是表示 a 由 Foo“构造”。实际上,.constructor 引用同样被委托给了 Foo.prototype,而 Foo.prototype.constructor 默认指向 Foo。a.constructor 只是通过默认的 [[Prototype]] 委托指向 Foo,这和“构造”毫无关系。

5.3 (原型)继承

  • 下面这段代码使用的就是典型的“原型风格”:
代码语言:javascript
复制
function Foo(name) { 
    this.name = name;
}
Foo.prototype.myName = function() { 
    return this.name;
};
function Bar(name,label) { 
    Foo.call( this, name ); 
    this.label = label;
}
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype 
Bar.prototype = Object.create( Foo.prototype );
// 注意!现在没有 Bar.prototype.constructor 了 
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() { 
    return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"

这段代码的核心部分就是语句 Bar.prototype = Object.create( Foo.prototype )。调用 Object.create(..)凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象(本例中是 Foo.prototype)。

  • ES6 添加了辅助函数 Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改对象的 [[Prototype]] 关联。 我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法: // ES6 之前需要抛弃默认的 Bar.prototype Bar.ptototype = Object.create( Foo.prototype ); // ES6 开始可以直接修改现有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype );

5.4 对象关联

[[Prototype]] 机制就是存在于对象中的一个内部链接,它会引用其他 对象。 通常来说,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototype]] 关联的对象上进行查找。同理,如果在后者中也没有找到需要的 引用就会继续查找它的 [[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。

5.4.1 创建关联
Object.create(..)
  • Object.create(..) 会创建一个新对象(bar)并把它关联到我们指定的对象(foo),这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦(比如使用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。
  • Object.create(null) 会 创 建 一 个 拥 有 空( 或 者 说 null)[[Prototype]] 链接的对象,这个对象无法进行委托。由于这个对象没有原型链,所以 instanceof 操作符(之前解释过)无法进行判断,因此总是会返回 false。
  • Object.create()的polyfill代码(兼容旧IE)
代码语言:javascript
复制
if (!Object.create) { 
    Object.create = function(o) {
    function F(){} 
    F.prototype = o; 
    return new F();
}; }
  • Object.create()的扩展 Object.create(..) 的第二个参数指定了需要添加到新对象中的属性名以及这些属性的属性描述符: var anotherObject = { a:2 }; var myObject = Object.create( anotherObject, { b: { enumerable: false, writable: true, configurable: false, value: 3 }, c: { enumerable: true, writable: false, configurable: false, value: 4 } }); myObject.hasOwnProperty( "a" ); // false myObject.hasOwnProperty( "b" ); // true myObject.hasOwnProperty( "c" ); // true myObject.a; // 2 myObject.b; // 3 myObject.c; // 4
小结

如果要访问对象中并不存在的一个属性,[[Get]] 操作就会查找对象内部[[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的作用域链),在查找属性时会对它进行遍历。 所有普通对象都有内置的 Object.prototype,指向原型链的顶端(比如说全局作用域),如 果在原型链中找不到指定的属性就会停止。toString()、valueOf() 和其他一些通用的功能 都存在于 Object.prototype 对象上,因此语言中所有的对象都可以使用它们。 关联两个对象最常用的方法是使用 new 关键词进行函数调用,在调用的 4 个步骤(第 2 章)中会创建一个关联其他对象的新对象。 使用 new 调用函数时会把新对象的 .prototype 属性关联到“其他对象”。带 new 的函数调用 通常被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不一样。 虽然这些 JavaScript 机制和传统面向类语言中的“类初始化”和“类继承”很相似,但 是 JavaScript 中的机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的 [[Prototype]] 链关联的。 出于各种原因,以“继承”结尾的术语(包括“原型继承”)和其他面向对象的术语都无 法帮助你理解 JavaScript 的真实机制(不仅仅是限制我们的思维模式)。

第6章 行为委托

类模型(面向对象风格)
代码语言:javascript
复制
function Foo(who) { 
    this.me = who;
}
Foo.prototype.identify = function() {
    return "I am " + this.me; 
};
function Bar(who) { 
    Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" ); 
b1.speak();
b2.speak();

委托模型(对象关联风格)

代码语言:javascript
复制
Foo = {
    init: function(who) {
        this.me = who; 
    },
    identify: function() {
        return "I am " + this.me;
    } 
};
Bar = Object.create( Foo );
Bar.speak = function() {
    alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
小结

在软件架构中你可以选择是否使用类和继承设计模式。大多数开发者理所当然地认为类是 唯一(合适)的代码组织方式,但是本章中我们看到了另一种更少见但是更强大的设计模式:行为委托。 行为委托认为对象之间是兄弟关系,互相委托,而不是父类和子类的关系。JavaScript 的 [[Prototype]] 机制本质上就是行为委托机制。也就是说,我们可以选择在 JavaScript 中努 力实现类机制(参见第 4 和第 5 章),也可以拥抱更自然的 [[Prototype]] 委托机制。 当你只用对象来设计代码时,不仅可以让语法更加简洁,而且可以让代码结构更加清晰。 对象关联(对象之前互相关联)是一种编码风格,它倡导的是直接创建和关联对象,不把它们抽象成类。对象关联可以用基于 [[Prototype]] 的行为委托非常自然地实现。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.语法
  • new的工作原理:
  • 2.类型
  • 内置对象
  • 3.内容(对象的属性)
    • 可计算属性名
      • 复制对象
        • 3.属性描述符
          • 4.【Get】& 【put】
            • 5.【Getter】& 【Setter】
            • 遍历
            • 第4章 混合对象“类”
              • 4.1.1 “类”设计模式
                • 4.2 类的机制
                  • 4.3.1 多态
                  • 第5章 原型
                    • 5.1 [[Prototype]]
                      • 5.1.1 [[Prototype]] 的“尽头”
                        • 5.1.2 属性设置和屏蔽
                        • 5.2 “类”
                          • 5.2.1 “类”函数
                            • 5.2.2 “构造函数”
                            • 5.3 (原型)继承
                            • 5.4 对象关联
                              • 5.4.1 创建关联
                                • 小结
                                • 第6章 行为委托
                                  • 小结
                                  相关产品与服务
                                  容器服务
                                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档