《JavaScript面向对象精要》读书笔记

JavaScript(ES5)的面向对象精要

标签: JavaScript 面向对象 读书笔记

2016年1月16日-17日两天看完了《JavaScript面向对象精要》(列入异步社区的运动送的),这本书虽然不够100页,但都是英华,不愧是《JavaScript高等程序设想》作者 Nicholas C.Zakas 的最新力作。

下面是我的读书笔记(ES5):

1.原始范例和援用范例

1.1 什么是范例

原始范例 保留为简朴数据值。
援用范例 保留为对象,其本质是指向内存位置的援用。

为了让开辟者能够把原始范例和援用范例按雷同的体式格局处置惩罚,JavaScript花费了很大的勤奋来保证言语的一致性。

其他编程言语用栈存原始范例,用对存储援用范例。而JavaScript则完整差异:它运用一个变量对象追踪变量的生存期。原始值被直接保留在变量对象内,而援用值则作为一个指针保留在变量对象内,该指针指向现实对象在内存中的存储位置。

1.2 原始范例

原始范例代表照原样保留的一些简朴数据。
JavaScript共有 5 种原始范例:

  • boolean 布尔,值为 true or false

  • number 数字,值为任何整型或浮点数值

  • string 字符串,值为由单引号或双引号括住的单个字符或一连字符

  • null 空范例,唯逐一个值:null

  • undefined 未定义,只要一个值:undefined(undefined会被赋给一个还没有初始化的变量)

JavaScript和许多其他言语一样,原始范例的变量直接保留原始值(而不是一个指向对象的指针)。


var color1 = "red";
var color2 = color1;

console.log(color1); // "red"
console.log(color2); // "red"

color1 = "blue";

console.log(color1); // "blue"
console.log(color2); // "red"

判别原始范例

判别原始范例的最好体式格局是运用 typeof 操纵符。


console.log(typeof "Nicholas"); // "string"
console.log(typeof 10);         // "number"
console.log(typeof true);       // "boolean"
console.log(typeof undefined);  // "undefined"

至于空范例(null)则有些辣手。


console.log(typeof null); // "object"

关于 typeof null,效果是”object”。(实在这已被设想和保护JavaScript的委员会TC39认定是一个毛病。在逻辑上,你能够认为 null 是一个空的对象指针,所以效果为”object”,但这照样很令人困惑。)

推断一个值是不是为空范例(null)的最好体式格局是直接和 null 比较:

console.log(value === null); // true or false

注重:以上这段代码运用了三等号(全等===),因为三等号(全等)不会将变量强迫转换为另一种范例。

console.log("5" == 5); // true
console.log("5" === 5); // false

console.log(undefined == null); // true
console.log(undefined === null); // false

原始要领

虽然字符串、数字和布尔值是原始范例,然则它们也具有要领(null和undefined没有要领)。

var name = "Nicholas";
var lowercaseName = name.toLowerCase(); // 转为小写

var count = 10;
var fixedCount = count.toFixed(2); // 转为10.00

var flag = true;
var stringFlag = flag.toString(); // 转为"true"

console.log("YIBU".charAt(0)); // 输出"Y"

只管原始范例具有要领,但它们不是对象。JavaScript使它们看上去像对象一样,以此来进步言语上的一致性体验。

1.3 援用范例

援用范例是指JavaScript中的对象,同时也是你在该言语中能找到最接近类的东西。
援用值是援用范例的实例,也是对象的同义词(背面将用对象指代援用值)。对象是属性的无序列表。属性包含键(一直是字符串)和值。假如一个属性的值是函数,它就被称为要领。除了函数能够运转之外,一个包含数组的属性和一个包含函数的属性没有什么区分。

建立对象

有时候,把JavaScript对象设想成哈希表能够协助你更好地明白对象组织。

《《JavaScript面向对象精要》读书笔记》

JavaScript 有好几种要领能够建立对象,或许说实例化对象。第一种是运用 new 操纵符和组织函数。
组织函数就是经过历程 new 操纵符来建立对象的函数——任何函数都能够是组织函数。依据定名范例,JavaScript中的组织函数用首字母大写来跟非组织函数举行辨别。


var object = new Object();

因为援用范例不再变量中直接保留对象,所以本例中的 object 变量现实上并不包含对象的实例,而是一个指向内存中现实对象地点位置的指针(或许说援用)。这是对象和原始值之间的一个基本差异,原始值是直接保留在变量中。

当你将一个对象赋值给变量时,现实是赋值给这个变量一个指针。这意味着,将一个变量赋值给别的一个变量时,两个变量各获得了一份指针的拷贝,指向内存中的统一个对象。

var obj1 = new Object();
var obj2 = obj1;

《《JavaScript面向对象精要》读书笔记》

对象援用消除

JavaScript言语有渣滓网络的功用,因而当你运用援用范例时无需忧郁内存分派。但最幸亏不运用对象时将其援用消除,让渣滓网络器对那块内存举行开释。消除援用的最好手腕是将对象变量设置为 null

var obj1 = new Object();
// dosomething
obj1 = null; // dereference

增加删除属性

在JavaScript中,你能够随时增加和删除其属性。

var obj1 = new Object();
var obj2 = obj1;

obj1.myCustomProperty = "Awsome!";
console.log(obj2.myCustomProperty); // "Awsome!" 因为obj1和obj2指向统一个对象。

1.4 内建范例实例化

内建范例以下:

  • Array 数组范例,以数字为索引的一组值的有序列表

  • Date 日期和时候范例

  • Error 运转期毛病范例

  • Function 函数范例

  • Object 通用对象范例

  • RegExp 正则表达式范例

可运用 new 来实例化每一个内建援用范例:


var items = new Array();
var new = new Date();
var error = new Error("Something bad happened.");
var func = new Function("console.log('HI');");
var object = new Object();
var re = new RegExp();

字面情势

内建援用范例有字面情势。字面情势许可你在不须要运用 new 操纵符和组织函数显现建立对象的状况下天生援用值。属性的能够是标识符或字符串(若含有空格或其他迥殊字符)


var book = {
    name: "Book_name",
    year: 2016
}

上面代码与下面这段代码等价:

var book = new Object();
book.name = "Book_name";
book.year = 2016;

虽然运用字面情势并没有挪用 new Object(),然则JavaScript引擎背地做的事情和 new Object()一样,除了没有挪用组织函数。其他援用范例的字面情势也是云云。

1.5 接见属性

可经过历程 .中括号 接见对象的属性。
中括号[]在须要动态决议接见哪一个属性时,迥殊有用。因为你能够用变量而不是字符串字面情势来指定接见的属性。

1.6 判别援用范例

函数是最轻易判别的援用范例,因为对函数运用 typeof 操纵符时,返回”function”。


function reflect(value){
    return value;
}
console.log(typeof reflect); // "function"

对其他援用范例的判别则较为辣手,因为关于一切非函数的援用范例,typeof 返回 object。为了更方便地判别援用范例,能够运用 JavaScript 的 instanceof 操纵符。


var items = [];
var obj = {};
function reflect(value){
    return value;
}

console.log(items instanceof Array); // true;
console.log(obj instanceof Object); // true;
console.log(reflect instanceof Function); // true;

instanceof 操纵符可判别继续范例。这意味着一切对象都是 Oject 的实例,因为一切援用范例都继续自 Object

虽然 instanceof 能够判别对象范例(如数组),然则有一个列外。JavaScript 的值能够在统一个网页的不必框架之间传来传去。因为每一个网页具有它本身的全局上下文——Object、Array以及其他内建范例的版本。所以当你把一个对象(如数组)从一个框架传到别的一个框架时,instanceof就没法辨认它。

1.8 原始封装范例

原始封装范例有 3 种:String、Number 和 Boolean。
当读取字符串、数字或布尔值时,原始封装范例将被自动建立。


var name = "Nicholas";
var firstChar = name.charAt(0); // "N"

这在背地发作的事变以下:


var name = "Nichola";
var temp = new String(name);
var firstChar = temp.charAt(0);
temp = null;

因为第二行把字符串当做对象运用,JavaScript引擎建立了一个字符串的实体让 charAt(0) 能够事情。字符串对象的存在仅用于该语句并在随后烧毁(一种被称为自动打包的历程)。为了测试这一点,试着给字符串增加一个属性看看它是不是对象。


var name = "Nicholas";
name.last = "Zakas";

console.log(name.last); // undefined;

下面是在JavaScript引擎中现实发作的事变:


var name = "Nicholas";
var temp = new String(name);
temp.last = "Zakas";
temp = null; // temporary object destroyed

var temp = new String(name);
console.log(temp.last);
temp = null;

新属性 last 现实上是在一个马上就被烧毁的暂时对象上而不是字符串上增加。以后当你试图接见该属性时,另一个差异的暂时对象被建立,而新属性并不存在。

虽然原始封装范例会被自动建立,在这些值上举行 instanceof 搜检对应范例的返回值倒是 false
这是因为暂时对象仅在值被读取时建立instanceof 操纵符并没有真的读取任何东西,也就没有暂时对象的建立。

固然你也能够手动建立原始封装范例。


var str = new String("me");
str.age = 18;

console.log(typeof str); // object
console.log(str.age); // 18

如你所见,手动建立原始封装范例现实会建立出一个 object。这意味着 typeof 没法判别出你现实保留的数据的范例。

别的,手动建立原始封装范例和运用原始值是有肯定区分的。所以只管防止运用。


var found = new Boolean(false);
if(found){
    console.log("Found"); // 实行到了,只管对象的值为 false
}

这是因为一个对象(如 {} )在前提推断语句中总被认为是 true;

MDN:Any object whose value is not undefined or null, including a Boolean oject whose value is false, evaluates to true when passed to a conditional statement.

1.9 总结

第一章的东西都是我们一些比较熟习的学问。然则也有一些须要注重的处所:

  • 正确辨别原始范例和援用范例

  • 关于 5 种原始范例都能够用typeof来判别,而空范例必需直接跟 null 举行全等比较。

  • 函数也是对象,可用 typeof 判别。别的援用范例,可用 instanceof 和一个组织函数来判别。(固然能够用 Object.prototype.toString.call() 判别,它会返回[object Array]之类的)。

  • 为了让原始范例看上去更像援用范例,JavaScript供应了 3 种封装范例。JavaScript会在背地建立这些对象使得你能够像运用平常对象那样运用原始值。但这些暂时对象在运用它们的语句结束时就马上被烧毁。虽然可手动建立,但不发起。

2. 函数

函数也是对象,使对象差异于别的对象的决议性特点是函数存在一个被称为 [[Call]] 的内部属性。
内部属性没法经过历程代码接见而是定义了代码实行时的行动。ECMAScript为JavaScript的对象定义了多种内部属性,这些内部属性都用两重中括号来标注

[[Call]]属性是函数独占的,表明该对象能够被实行。因为仅函数具有该属性,ECMAScript 定义typeof操纵符对任何具有[[Call]]属性的对象返回”function”。过去因某些浏览器曾在正则表达式中包含 [[Call]] 属性,致使正则表达式被毛病判别为函数。

2.1 声明照样表达式

二者的一个主要区分是:函数声明会被提拔至上下文(要么是该函数被声明时地点的函数局限,要么是全局局限)的顶部。

2.2 函数就是值

能够像运用对象一样运用函数(因为函数原本就是对象,Function组织函数越发轻易申明)。

2.3 参数

函数参数保留在类数组对象 argumentArray.isArray(arguments) 返回 false)中。能够吸收恣意数目的参数。
函数的 length 属性表明其希冀的参数个数。

2.4 重载

大多数面向对象言语支撑函数重载,它能让一个函数具有多个署名。函数署名由函数的名字、参数的个数及其范例构成。
而JavaScript能够吸收恣意数目的参数且参数范例完整没有限定。这申明JavaScript函数基本就没有署名,因而也不存在重载。


function sayMessage(message){
    console.log(message);
}
function sayMessage(){
    console.log("Default Message");
}

sayMessage("Hello!"); // 输出"Default Message";

在Javscript里,当你试图定义多个同名的函数时,只要末了的定义有用,之前的函数声明被完整删除(函数也是对象,变量只是存指针)。

var sayMessage = new Function("message", "console.log(message)");
var sayMessage = new Function("console.log(\"Default Message\");");

sayMessage("Hello!"); 

固然,你能够依据传入参数的数目来模拟重载。

2.5 对象要领

对象的值是函数,则该属性被称为要领。

2.5.1 this对象

JavaScript 一切的函数作用域内都有一个 this 对象代表挪用该函数的对象。在全局作用域中,this 代表全局对象(浏览器里的window)。当一个函数作为对象的要领挪用时,默许 this 的值等于该对象。
this在函数挪用时才被设置。

function sayNameForAll(){
    console.log(this.name);
}

var person1 = {
    name: "Nicholas",
    sayName: sayNameForAll
}

var name = "Jack";

person1.sayName(); // 输出 "Nicholas"
sayNameforAll(); // 输出 "Jack"

2.5.2 转变this

3 种函数要领运转你转变 this 值。

  1. fun.call(thisArg[, arg1[, arg2[, …]]]);

  2. fun.apply(thisArg, [argsArray]);

  3. fun.bind(thisArg[, arg1[, arg2[, …]]])

运用 callapply 要领,就不须要将函数到场每一个对象——你显现地指定了 this 的值而不是让JavaScript引擎自动指定。

callapply 的差异处所是,call 须要把一切参数一个个列出来,而 apply 的参数须要一个数组或许类似数组的对象(如 arguments 对象)。

bind 是ECMAScript 5 新增的,它会建立一个新函数返回。其参数与 call 类似,而且其一切参数代表须要被永远设置在新函数中的定名参数(绑定了的参数(没绑定的参数依旧能够传入),就算挪用时再传入别的参数,也不会影响这些绑定的参数)。

function sayNameForAll(label){
    console.log(label + ":" + this.name);
}
var person = {
    name: "Nicholas"
}

var sayNameForPerson = sayNameForAll.bind(person);
sayNameForPerson("Person"); // 输出"Person:Nicholas"

var sayName = sayNameForAll.bind(person, "Jc");

sayName("change"); // 输出"Jc:Nicholas" 因为绑定的形参,会疏忽挪用时再传入参数

2.6 总结

  • 函数也是对象,所以它能够被接见、复制和掩盖。

  • 函数与其他对象最大的区分在于它们有一个迥殊的内部属性 [[Call]],包含了该函数的实行指令。

  • 函数声明会被提拔至上下文的顶部。

  • 函数是对象,所以存在一个 Function 组织函数。但这会使你的代码难以明白和调试,除非函数的实在情势要直到运转时才肯定的时候才会应用它。

明白对象

JavaScript中的对象是动态的,可在代码实行的恣意时候发作转变。基于类的言语会依据类的定义锁定对象。

3.1 定义属性

当一个属性第一次被增加到对象时,JavaScript会在对象上挪用一个名为 [[Put]] 的内部要领。[[Put]] 要领会在对象上建立一个新节点来保留属性。
当一个已有的属性被给予一个新值时,挪用的是一个名为 [[Set]] 的要领。

3.2 属性探测

搜检对象是不是已有一个属性。JavaScript开辟新手毛病地运用以下形式检测属性是不是存在。


if(person.age){
    // do something with ag
}

上面的题目在于JavaScript的范例强迫会影响该形式的输出效果。
当if推断中的值以下时,会推断为

  • 对象

  • 非空字符串

  • 非零

  • true

当if推断中的值以下时,会推断为

  • null

  • undefined

  • 0

  • false

  • NaN

  • 空字符串

因而推断属性是不是存在的要领是运用 in 操纵符。
in 操纵符会搜检自有属性和原型属性
一切的对象都具有的 hasOwnProperty() 要领(现实上是 Object.prototype 原型对象的),该要领在给定的属性存在且为自有属性时返回 true

var person = {
    name: "Nicholas"
}

console.log("name" in person); // true
console.log(person.hasOwnpropert("name")); // true

console.log("toString" in person); // true
console.log(person.hasOwnproperty("toString")); // false

3.3 删除属性

设置一个属性的值为 null 并不能从对象中完全移除谁人属性,这只是挪用 [[Set]]null 值替代了该属性本来的值罢了。
delete 操纵符针对单个对象属性挪用名为 [[Delete]] 的内部要领。删除胜利时,返回 true

var person = {
    name: "Nicholas"
}

person.name = null;
console.log("name" in person); // true
delete person.name;
console.log(person.name); // undefined 接见一个不存在的属性将返回 undefined
console.log("name" in person); // false

3.4 属性罗列

一切工资增加的属性默许都是可罗列的。可罗列的内部特征 [[Enumerable]] 都被设置为 true
for-in 轮回会罗列一个对象一切的可罗列属性。

我的备注:在Chrome中,对象属性会按ASCII表排序,而不是定义时的递次。

ECMAScript 5 的 Object() 要领能够猎取可罗列属性的名字的数组。

var person = {
    name: "Ljc",
    age: 18
}

Object.keys(person); // ["name", "age"];

for-inObject.keys() 的一个区分是:前者也会遍历原型属性,而后者返回自有(实例)属性。

现实上,对象的大部分原生要领的 [[Enumerable]] 特征都被设置为 false。可用 propertyIsEnumerable() 要领搜检一个属性是不是为可罗列的。

var arr = ["abc", 2];
console.log(arr.propertyIsEnumerable("length")); // false

3.5 属性范例

属性有两种范例:数据属性接见器属性
数据属性包含一个值。[[Put]] 要领的默许行动是建立数据属性
接见器属性不包含值而是定义了一个当属性被读取时挪用的函数(称为getter)和一个当属性被写入时挪用的函数(称为setter)。接见器属性仅须要 gettersetter 二者中的恣意一个,固然也能够二者。


// 对象字面情势中定义接见器属性有迥殊的语法:
var person = {
    _name: "Nicholas",
    
    get name(){
        console.log("Reading name");
        return this._name;
    },
    set name(value){
        console.log("Setting name to %s", value);
        this._name = value;
    }
};

console.log(person.name); // "Reading name" 然后输出 "Nicholas"

person.name = "Greg";
console.log(person.name); // "Setting name to Greg" 然后输出 "Greg"

前置下划线_ 是一个约定俗成的定名范例,示意该属性是私有的,现实上它照样公然的。

接见器就是定义了我们在对象读取或设置属性时,触发的行动(函数),_name 相当于一个内部变量。
当你愿望赋值(读取)操纵会触发一些行动,接见器就会异常有用。

当只定义getter或setter其一时,该属性就会变成只读或只写。

3.6 属性特征

在ECMAScript 5 之前没有办法指定一个属性是不是可罗列。现实上基本没有要领接见属性的任何内部特征。为了转变这点,ECMAScript 5引入了多种要领来和属性特征值直接互动。

3.6.1 通用特征

数据属性和接见器属性均由以下两个属性特制:
[[Enumerable]] 决议了是不是能够遍历该属性;
[[Configurable]] 决议了该属性是不是可设置。

一切工资定义的属性默许都是可罗列、可设置的。

能够用 Object.defineProperty() 要领转变属性特征。
其参数有三:具有该属性的对象、属性名和包含须要设置的特征的属性形貌对象。


var person = {
    name: "Nicholas"
}
Object.defineProperty(person, "name", {
    enumerable: false
})

console.log("name" in person); // true
console.log(person.propertyIsEnumerable("name")); // false

var properties = Object.keys(person);
console.log(properties.length); // 0

Object.defineProperty(person, "name",{
    configurable: false
})

delete person.name; // false
console.log("name" in person); // true

Object.defineProperty(person, "name",{ // error! 
// 在 chrome:Uncaught TypeError: Cannot redefine property: name
    configurable: true
})

没法将一个不可设置的属性变成可设置,相反则能够。

3.6.2 数据属性特征

数据属性分外具有两个接见器属性不具备的特征。
[[Value]] 包含属性的值(哪怕是函数)。
[[Writable]] 布尔值,指点该属性是不是可写入。一切属性默许都是可写的。

var person = {};

Object.defineProperty(person, "name", {
    value: "Nicholas",
    enumerable: true,
    configurable: true,
    writable: true
})

Object.defineProperty() 被挪用时,假如属性原本就有,则会根据新定义属性特征值去掩盖默许属性特征(enumberableconfigurablewritable 均为 true)。但假如用该要领定义新的属性时,没有为一切的特征值指定一个值,则一切布尔值的特征值会被默许设置为 false。即不可罗列、不可设置、不可写的。
当你用 Object.defineProperty() 转变一个已有的属性时,只要你指定的特征会被转变。

3.6.3 接见器属性特征

接见器属性分外具有两个特征。[[Get]][[Set]],内含 gettersetter 函数。
运用接见其属性特征比运用对象字面情势定义接见器属性的上风在于:能够为已有的对象定义这些属性。而后者只能在建立时定义接见器属性。

var person = {
    _name: "Nicholas"
};

Object.defineProperty(person, "name", {
    get: function(){
        return this._name;
    },
    set: function(value){
        this._name = value;
    },
    enumerable: true,
    configurable: true
})

for(var x in person){
    console.log(x); // _name \n(换行) name(接见器属性)
}

设置一个不可设置、不可罗列、不能够写的属性:

Object.defineProperty(person, "name",{
    get: function(){
        return this._name;
    }
})

关于一个新的接见器属性,没有显现设置值为布尔值的属性,默认为 false

3.6.4 定义多重属性

Object.defineProperties() 要领能够定义恣意数目的属性,以至能够同时转变已有的属性并建立新属性。


var person = {};

Object.defineProperties(person, {
    
    // data property to store data
    _name: {
        value: "Nicholas",
        enumerable: true,
        configurable: true,
        writable: true
    },
    
    // accessor property
    name: {
        get: function(){
            return this._name;
        },
        set: function(value){
            this._name = value;
        }
    }
})

3.6.5 猎取属性特征

Object.getOwnPropertyDescriptor() 要领。该要领接收两个参数:对象和属性名。假如属性存在,它会返回一个属性形貌对象,内在4个属性:configurableenumerable,别的两个属性则依据属性范例决议。

var person = {
    name: "Nicholas"
}

var descriptor = Object.getOwnPropertyDescriptor(person, "name");

console.log(descriptor.enumerable); // true
console.log(descriptor.configuable); // true
console.log(descriptor.value); // "Nicholas"
console.log(descriptor.wirtable); // true

3.7 制止修正对象

对象和属性一样具有指点其行动的内部特征。个中, [[Extensible]] 是布尔值,指明该对象本身是不是能够被修正。默许是 true。当值为 false 时,就可以制止新属性的增加。

发起在 “use strict”; 严厉形式下举行。

3.7.1 制止扩大

Object.preventExtensions() 建立一个不可扩大的对象(即不能增加新属性)。
Object.isExtensible() 搜检 [[Extensible]] 的值。

var person = {
    name: "Nocholas"
}

Object.preventExtensions(person);

person.sayName = function(){
    console.log(this.name)
}

console.log("sayName" in person); // false

3.7.2 对象封印

一个被封印的对象是不可扩大的且其一切属性都是不可设置的(即不能增加、删除属性或修正其属性范例(从数据属性变成接见器属性或相反))。只能读写它的属性
Object.seal()。挪用此要领后,该对象的 [[Extensible]] 特征被设置为 false,其一切属性的 [[configurable]] 特征被设置为 false
Object.isSealed() 推断一个对象是不是被封印。

3.7.3 对象凝结

被凝结的对象不能增加或删除属性,不能修正属性范例,也不能写入任何数据属性。简言而之,被凝结对象是一个数据属性都为只读的被封印对象。
Object.freeze() 凝结对象。
Object.isFrozen() 推断对象是不是被凝结。

3.8 总结

  • in 操纵符检测自有属性和原型属性,而 hasOwnProperty() 只搜检自有属性。

  • delete 操纵符删除对象属性。

  • 属性有两种范例:数据属性和接见器属性。

  • 一切属性都有一些相干特征。[[Enumerable]][[Configurable]] 的两种属性都有的,而数据属性尚有 [[Value]][[Writable]],接见器属性尚有 [[Get]][[Set]]。可经过历程 Object.defineProperty()Object.defineProperties() 转变这些特征。用 Object.getOwnPropertyDescriptor() 猎取它们。

  • 3 种能够锁定对象属性的体式格局。

4. 组织函数和原型对象

因为JavaScript(ES5)缺乏类,但可用组织函数和原型对象给对象带来与类类似的功用。

4.1 组织函数

组织函数的函数名首字母应大写,以此辨别其他函数。
当没有须要给组织函数通报参数,可疏忽小括号:


var Person = {
    // 有意留空
}
var person = new Person;

只管 Person 组织函数没有显式返回任何东西,但 new 操纵符会自动建立给定范例的对象并返回它们。

每一个对象在建立时都自动具有一个组织函数属性(constructor,现实上是它们的原型对象上的属性),个中包含了一个指向其组织函数的援用。
经过历程对象字面量情势({})或Object组织函数建立出来的泛用对象,其组织函数属性(constructor)指向 Object;而那些经过历程自定义组织函数建立出来的对象,其组织函数属性指向建立它的组织函数。


console.log(person.constructor === Person); // true
console.log(({}).constructor === Object); // true
console.log(([1,2,3]).constructor === Object); // true

// 证实 constructor是在原型对象上
console.log(person.hasOwnPrototype("constructor")); // false
console.log(person.constructor.prototype.hasOwnPrototype("constructor")); // true

只管对象实例及其组织函数之间存在如许的关联,但照样发起运用 instanceof 来搜检对象范例。这是因为组织函数属性能够被掩盖。(person.constructor = “”)。

当你挪用组织函数时,new 会自动自动建立 this 对象,且其范例就是组织函数的范例(组织函数就好像类,相当于一种数据范例)。

你也能够在组织函数中显式挪用 return。假如返回值是一个对象,它会替代新建立的对象实例而返回,假如返回值是一个原始范例,它会被疏忽,新建立的对象实例会被返回。

一直确保要用 new 挪用组织函数;不然,你就是在冒着转变全局对象的风险,而不是建立一个新的对象。

var person = Person("Nicholas"); // 缺乏 new

console.log(person instanceof Person); // false
console.log(person); // undefined,因为没用 new,就相当于一个平常函数,默许返回 undefined
console.log(name); // "Nicholas"

当Person不是被 new 挪用时,组织函数中的 this 对象等于全局 this 对象。

在严厉形式下,会报错。因为严厉形式下,并没有为全局对象设置 this,this 坚持为 undefined。

以下代码,经过历程 new 实例化 100 个对象,则会有 100 个函数做雷同的事。因而可用 prototype 同享统一个要领会更高效。

var person = {
    name: "Nicholas",
    sayName: function(){
        console.log(this.name);
    }
}

4.2 原型对象

能够把原型对象看做是对象的基类。险些一切的函数(除了一些内建函数)都有一个名为 prototype 的属性,该属性是一个原型对象用来建立新的对象实例。一切建立的对象实例(统一组织函数,固然,能够接见上层的原型对象)同享该原型对象,且这些对象实例能够接见原型对象的属性。比方,hasOwnProperty()定义在 Object 的原型对象中,但却可被任何对象看成本身的属性接见。

var book = {
    title : "book_name"
}

"hasOwnProperty" in book; // true
book.hasOwnProperty("hasOwnProperty"); // false
Object.property.hasOwnProperty("hasOwnProperty"); // true

判别一个原型属性


function hasPrototypeProperty(object, name){
    return name in object && !object.hasOwnProperty(name);
}

4.2.1 [[Prototype]] 属性

一个对象实例经过历程内部属性 [[Prototype]] 跟踪其原型对象。该属性是一个指向该实例运用的原型对象的指针。当你用 new 建立一个新的对象时,组织函数的原型对象就会被赋给该对象的 [[Prototype]] 属性。

《《JavaScript面向对象精要》读书笔记》

由上图能够看出,[[Prototype]] 属性是怎样让多个对象实例援用统一个原型对象来削减反复代码。

Object.getPrototypeOf() 要领可读取 [[Prototype]] 属性的值。


var obj = {};
var prototype = Object.getPrototypeOf(Object);

console.log(prototype === Object.prototype); // true

大部分JavaScript引擎在一切对象上都支撑一个名为 _proto_ 的属性。该属性使你能够直接读写 [[Prototype]] 属性。

isPrototypeOf() 要领会搜检某个对象是不是是另一个对象的原型对象,该要领包含在一切对象中。

var obj = {}
console.log(Object.prototype.isPrototypeOf(obj)); // true

当读取一个对象的属性时,JavaScript 引擎首先在该对象的自有属性查找属性名。假如找到则返回。不然会搜刮 [[Prototype]] 中的对象,找到则返回,找不到则返回 undefined。

var obj = new Object();
console.log(obj.toString()); // "[object Object]"

obj.toString = function(){
    return "[object Custom]";
}
console.log(obj.toString()); // "[object Custom]"

delete obj.toString; // true
console.log(obj.toString()); // "[object Object]"

delete obj.toString; // 无效,delete不能删除一个对象从原型继续而来的属性
cconsole.log(obj.toString()); // // "[object Object]"

MDN:delete 操纵符不能删除的属性有:①显式声明的全局变量不能被删除,该属性不可设置(not configurable); ②内置对象的内置属性不能被删除; ③不能删除一个对象从原型继续而来的属性(不过你能够从原型上直接删掉它)。

一个主要观点:没法给一个对象的原型属性赋值。我认为是没法直接增加吧,在chrome和Edge中,都没法读取_proto_属性,但我们能够经过历程 obj.constructor.prototype.sayHi = function(){console.log("Hi!")} 向原型对象增加属性。

《《JavaScript面向对象精要》读书笔记》
(图片中心能够看出,为对象obj增加的toString属性替代了原型属性)

4.2.2 在组织函数中运用原型对象

在原型对象上定义公用要领
在原型对象上定义数据范例

开辟中须要注重原型对象的数据是不是同享。

function Person(name){
    this.name = name
}

Person.prototype.sayName = function(){
    console.log(this.name);
}

Person.prototype.position = "school";
Person.prototype.arr = [];

var person1 = new Person("xiaoming");
var person2 = new Person("Jc");

console.log("原始范例")
console.log(person1.position); // "school"
console.log(person2.position); // "school"

person1.position = 2; // 这是在当前属性设置position,援用范例同理
console.log(person1.hasOwnProperty("position")); // true
console.log(person2.hasOwnProperty("position")); // false

console.log("援用范例");
person1.arr.push("pizza"); // 这是在原型对象上设置,而不是直接在对象上
person2.arr.push("quinoa"); // 这是在原型对象上设置
console.log(person1.hasOwnProperty("arr")); // false
console.log(person2.hasOwnProperty("arr")); // false
console.log(person1.arr); // ["pizza", "quinoa"]
console.log(person2.arr); // ["pizza", "quinoa"]

上面是在原型对象上逐一增加属性,下面一种更简约的体式格局:以一个对象字面情势替代原型对象

function Person(name){
    this.name
}

Person.prototype = {
    sayName: function(){
        console.log(this.name);
    },
    toString: function(){
        return "[Person ]" + this.name + "]";
    }
}

这类体式格局有一种副作用:因为原型对象上具有一个 constructor 属性,这是其他对象实例所没有的。当一个函数被建立时,它的 prototype 属性也会被建立,且该原型对象的 constructor 属性指向该函数。当运用字面量时,因没显式设置原型对象的 constructor 属性,因而其 constructor 属性是指向 Object 的。
因而,当经过历程此体式格局设置原型对象时,可手动设置 constructor 属性。


function Person(name){
    this.name
}

// 发起第一个属性就是设置其 constructor 属性。
Person.prototype = {
    constructor: Person,

    sayName: function(){
        console.log(this.name);
    },
    toString: function(){
        return "[Person ]" + this.name + "]";
    }
}

组织函数、原型对象和对象实例之间的关联最风趣的一方面也许是:
对象实例和组织函数直接没有直接联络。(对象实例只要 [[Prototype]] 属性(本身测试时不能读取(_proto_))指向其响应的原型对象,而原型对象的 constructor 属性指向组织函数,而组织函数的 prototype 指向原型对象)
《《JavaScript面向对象精要》读书笔记》

4.2.3 转变原型对象

因为每一个对象的 [[Prototype]] 只是一个指向原型对象的指针,所以原型对象的修改会马上反应到一切援用它的对象。
当对一个对象运用封印 Object.seal() 或凝结 Object.freeze() 时,完整是在操纵对象的自有属性,但任然能够经过历程在原型对象上增加属性来扩大这些对象实例。

4.2.4 内建对象(如Array、String)的原型对象

String.prototype.capitalize = function(){
    return this.charAt(0).toUpperCase() + this.substring(1);
}

总结

  • 组织函数就是用 new 操纵符挪用的平常函数。可用过 instanceof 操纵符或直接接见 constructor(现实上是原型对象的属性) 来判别对象是被哪一个组织函数所建立的。

  • 每一个函数都有一个 prototype 对象,它定义了该组织函数建立的一切对象同享的属性。而 constructor 属性现实上是定义在原型对象里,供一切对象实例同享。

  • 每一个对象实例都有 [[Prototype]] 属性,它是指向原型对象的指针。当接见对象的某个属性时,先从对象本身查找,找不到的话就到原型对象上找。

  • 内建对象的原型对象也可被修正

5. 继续

5.1 原型对象链和 Object.prototype

JavaScript内建的继续要领被称为 原型对象链(又叫原型对象继续)。
原型对象的属性可经过对象实例接见,这就是继续的一种情势。对象实例继续了原型对象的属性,而原型对象也是一个对象,它也有本身的原型对象并继续其属性,以此类推。这就是原型对象链。

一切对象(包含自义定的)都自动继续自 Object,除非你尚有指定。更确切地说,一切对象都继续自 Object.prototype。任何以对象字面量情势定义的对象,其 [[Prototype]] 的值都被设为 Object.prototype,这意味着它继续 Object.prototype 的属性。

5.1.1 继续自 Object.prototype 的要领

Object.prototype 平常有以下几个要领

  • hasOwnProperty() 检测是不是存在一个给定名字的自有属性

  • propertyIsemumerable() 搜检一个自有属性是不是可罗列

  • isPrototypeOf 搜检一个对象是不是是另一个对象的原型对象

  • valueOf() 返回一个对象的值表达

  • toString() 返回一个对象的字符串表达

这 5 种要领经过继续出现在一切对象中。
因为一切对象都默许继续自 Object.prototype,所以转变它就会影响一切的对象。所以不发起。

5.2 继续

对象继续是最简朴的继续范例。你唯须要做的是指定哪一个对象是新对象的 [[Prototype]]。对象字面量情势会隐式指定 Object.prototype 为其 [[Protoype]]。固然我们能够用 ES5 的 Object.create() 要领显式指定。该要领接收两个参数,第一个是新对象的的 [[Prototype]] 所指向的对象。第二个参数是可选的一个属性形貌对象,其花样与 Object.definePrototies()一样。

var obj = {
    name: "Ljc"
};

// 等同于
var obj = Object.create(Object.prototype, {
    name: {
        value: "Ljc",
        configurable: true,
        enumberable: true,
        writable: true
    }
});

下面是继续别的对象:


var person = {
    name: "Jack",
    sayName: function(){
        console.log(this.name);
    }
}

var student = Object.create(person, {
    name:{
        value: "Ljc"
    },
    grade: {
        value: "fourth year of university",
        enumerable: true,
        configurable: true,
        writable: true
    }
});

person.sayName(); // "Jack"
student.sayName(); // "Ljc"

console.log(person.hasOwnProperty("sayName")); // true
console.log(person.isPrototypeOf(student)); // true
console.log(student.hasOwnProperty("sayName")); // false
console.log("sayName" in student); // true

《《JavaScript面向对象精要》读书笔记》

当接见一个对象属性时,JavaScript引擎会实行一个搜刮历程。假如在对象实例存在该自有属性,则返回,不然,依据其私有属性 [[Protoype]] 所指向的原型对象举行搜刮,找到返回,不然继续上述操纵,晓得继续链末尾。末尾一般是 Object.prototype,其 [[Prototype]]null

固然,也能够用 Object.create() 罕见一个 [[Prototype]]null 的对象。


var obj = Object.create(null);

console.log("toString" in obj); // false

该对象是一个没有原型对象链的对象,等于一个没有预定义属性的白板。

5.3 组织函数继续

JavaScript 中的对象继续也是组织函数继续的基本。
第四章提到,险些一切函数都有 prototype 属性,它可被修正或替代。该 prototype 属性被自动设置为一个新的继续自 Object.prototype 的泛用对象,该对象(原型对象)有一个自有属性 constructor。现实上,JavaScript 引擎为你做了下面的事变。

// 你写成如许
function YourConstructor(){
    // initialization
}

// JavaScript引擎在背地为你做了这些处置惩罚
YourConstructor.prototype = Object.create(Object.prototype, {
    constructor: {
        configurable: true,
        enumerable: true,
        value: YourConstructor,
        writable: true
    }
})

你不须要做分外的事情,这段代码帮你把组织函数的 prototype 属性设置为一个继续自 Object.prototype 的对象。这意味着 YourConstructor 建立出来的任何对象都继续自 Object.prototype

因为 prototype 可写,你能够经过历程转变它来转变原型对象链。

MDN:instanceof 运算符能够用来推断某个组织函数的 prototype 属性是不是存在别的一个要检测对象的原型链上。

function Rectangle(length, width){
    this.length = length;
    this.width = width
}

Rectangle.prototype.getArea = function(){
    return this.length * this.width
}

Rectangle.prototype.toString = function(){
    return "[Rectangle " + this.length + "x" + this.width + "]";
}
// inherits from Rectangle
function Square(size){
    this.length = size;
    this.width = size;
}

Square.prototype = new Rectangle(); // 只管是 Square.prototype 是指向了 Rectangle 的对象实例,即Square的实例对象也能接见该实例的属性(假如你提早声清楚明了该对象,且给该对象新增属性)。
// Square.prototype = Rectangle.prototype; // 这类完成没有上面这类好,因为Square.prototype 指向了 Rectangle.prototype,致使修正Square.prototype时,现实就是修正Rectangle.prototype。
console.log(Square.prototype.constructor); // 输出 Rectangle 组织函数

Square.prototype.constructor = Square; // 重置回 Square 组织函数
console.log(Square.prototype.constructor); // 输出 Square 组织函数

Square.prototype.toString = function(){
    return "[Square " + this.length + "x" + this.width + "]";
}

var rect = new Rectangle(5, 10);
var square = new Square(6);

console.log(rect.getArea()); // 50
console.log(square.getArea()); // 36

console.log(rect.toString()); // "[Rectangle 5 * 10]", 但假如是Square.prototype = Rectangle.prototype,则这里会"[Square 5 * 10]"
console.log(square.toString()); // "[Square 6 * 6]"

console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
console.log(square instanceof Object); // true

《《JavaScript面向对象精要》读书笔记》

Square.prototype 并不真的须要被改成为一个 Rectangle 对象。事实上,是 Square.prototype 须要指向 Rectangle.prototype 使得继续得以完成。这意味着能够用 Object.create() 简化例子。

// inherits from Rectangle
function Square(size){
    this.length = size;
    this.width = size;
}

Square.prototype= Object.create(Rectangle.prototype, {
    constructor: {
        configurable: true,
        enumerable: true,
        value: Square,
        writable: true
    }
})

在对原型对象增加属性前要确保你已改成了原型对象,不然在改写时会丧失之前增加的要领(因为继续是将被继续对象赋值给须要继续的原型对象,相当于重写了须要继续的原型对象)。

5.4 组织函数盗取

因为JavaScript中的继续是经过历程原型对象链来完成的,因而不须要挪用对象的父类的组织函数。假如确切须要在子类组织函数中挪用父类组织函数,那就可以够在子类的组织函数中应用 callapply要领挪用父类的组织函数。

// 在上面的代码基本上作出修正
// inherits from Rectangle
function Square(size){
    Rectangle.call(this, size, size);
    
    // optional: add new properties or override existing ones here
}

平常来说,须要修正 prototyp 来继续要领并用组织函数盗取来设置属性,因为这类做法模拟了那些基于类的言语的类继续,所以这一般被称为伪类继续。

5.5 接见父类要领

实在也是经过历程指定 callapply 的子对象挪用父类要领。

6 对象形式

6.1 私有成员和特权成员

JavaScipt 对象的一切属性都是公有的,没有显式的要领指定某个属性不能被外界接见。

6.1.1 模块形式

模块形式是一种用于建立具有私有数据的单件对象的形式。
基本做法是运用马上挪用函数表达式(IIFE)来返回一个对象。道理是应用闭包。


var yourObj = (function(){
    // private data variables
    
    return {
        // public methods and properties
    }
}());

模块形式尚有一个变种叫暴露模块形式,它将一切的变量和要领都放在 IIFE 的头部,然后将它们设置到须要被返回的对象上。

//  平常写法
var yourObj = (function(){
    var age = 25;
    
    return {
        name: "Ljc",
        
        getAge: function(){
            return agel
        }
    }
}());

// 暴露模块形式
var yourObj = (function(){
    var age = 25;
    function getAge(){
        return agel
    };
    return {
        name: "Ljc",
        getAge: getAge
    }
}());

6.1.2 组织函数的私有成员(不能经过历程对象直接接见)

模块形式在定义单个对象的私有属性非常有用,但关于那些一样须要私有属性的自定义范例呢?你能够在组织函数中运用类似的形式来建立每一个实例的私有数据。

function Person(name){
    // define a variable only accessible inside of the Person constructor
    var age = 22;
    
    this.name = name;
    this.getAge = function(){
        return age;
    };
    this.growOlder = function(){
        age++;
    }
}

var person = new Person("Ljc");

console.log(person.age); // undefined
person.age = 100;
console.log(person.getAge()); // 22

person.growOlder();
console.log(person.getAge()); // 23

这里有个题目:假如你须要对象实例具有私有数据,就不能将响应要领放在 prototype 上。

假如你须要一切实例同享私有数据。则可连系模块形式和组织函数,以下:


var Person = (function(){
    var age = 22;

    function InnerPerson(name){
        this.name = name;
    }

    InnerPerson.prototype.getAge = function(){
        return age;
    }
    InnerPerson.prototype.growOlder = function(){
        age++;
    };

    return InnerPerson;
}());

var person1 = new Person("Nicholash");
var person2 = new Person("Greg");

console.log(person1.name); // "Nicholash"
console.log(person1.getAge()); // 22

console.log(person2.name); // "Greg"
console.log(person2.getAge()); // 22

person1.growOlder();
console.log(person1.getAge()); // 23
console.log(person2.getAge()); // 23

6.2 混入

这是一种伪继续。一个对象在不转变原型对象链的状况下得到了别的一个对象的属性被称为“混入”。因而,和继续差异,混入让你在建立对象后没法搜检属性泉源。
纯函数完成:


function mixin(receiver, supplier){
    for(var property in supplier){
        if(supplier.hasOwnProperty(property)){
            receiver[property] = supplier[property];
        }
    }
}

这是浅拷贝,假如属性的值是一个援用,那末二者将指向统一个对象。

6.3 作用域平安的组织函数

组织函数也是函数,所以不必 new 也能挪用它们来转变 this 的值。在非严厉形式下, this 被强迫指向全局对象。而在严厉形式下,组织函数会抛出一个毛病(因为严厉形式下没有为全局对象设置 thisthis 坚持为 undefined)。
而许多内建组织函数,比方 ArrayRegExp 不须要 new 也能一般事情,这是因为它们被设想为作用域平安的组织函数。
当用 new 挪用一个函数时,this 指向的新建立的对象是属于该组织函数所代表的自定义范例。因而,可在函数内用 instanceof 搜检本身是不是被 new 挪用。

function Person(name){
    if(this instanceof Person){
        // called with "new"
    }else{
        // called without "new"
    }
}

详细案例:


function Person(name){
    if(this instanceof Person){
        this.name = name;
    }else{
        return new Person(name);
    }
}

总结

看了两天的书,做了两天的笔记。固然这只是ES5的。过几天 ES6 新书又来了。末了谢谢 异步社区 送我这本好书 《JavaScript面向对象精要》,让我的前端基础越发稳定,愿望本身的前端之路越走越顺。

对应 GitHub

    原文作者:思考中
    原文地址: https://segmentfault.com/a/1190000004334910
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞