《JavaScript面向对象精要》阅读记录

1. 原始类型、引用类型
原始类型(boolean、string、number、null、undefined)的变量直接保存原始值;

引用类型的变量不在变量中直接保存对象,而是保存指向内存中实际对象所在位置的指针/引用;引用值(对象)是引用类型的实例;

把一个对象赋值给变量时,实际是赋值给这个变量一个指针。

null空类型只有一个值:null;

undefined未定义类型只有一个值:undefined

typeof null == 'object' :可以认为 null 是一个空的对象指针,所以结果为"object";

判断一个值是否为空类型(null)的最佳方式是直接和 null 比较(val===null)

对象是属性的无序列表。属性包含键(始终是字符串)和值。如果一个属性的值是函数,它就被称为方法;

对象引用解除: obj=null;  // 赋值为null即可


内建类型:Function、Object、Array、Date、RegExp、Error

可使用 new 来实例化每一个内建引用类型

字面量形式虽然没有用new 实例化,但是js引擎背后做的事情确是一样的

typeof能返回的类型:boolean、string、number、undefined、object、function

instanceof能鉴别继承类型:obj instanceof Object


原始封装类型:Boolean、String、Number;(为了让原始类型看上去更像引用类型)

当读取原始封装类型变量值时,原始封装类型在背后自动创建并随后销毁:

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;
//js引擎中实际发生的事:
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
注意:手动创建原始封装类型和使用原始值是有区别的。所以尽量避免手动创建原始封装类型。

var found = new Boolean(false);
if(found){
  console.log("Found"); // 执行到了,尽管对象的值为 false
}
// 这是因为一个对象(如 {} )在条件判断语句中总被认为是 true;


2. 函数
函数也是对象,使函数不同于其它对象的决定性特点是函数存在一个被称为 [[Call]] 的内部属性。内部属性无法通过代码访问而是定义了代码执行时的行为。ECMAScript为JavaScript的对象定义了多种内部属性,这些内部属性都用双重中括号来标注。

[[Call]] 属性是函数独有的,表明该对象可以被执行。由于仅函数拥有该属性,ECMAScript 定义 typeof 操作符对任何具有 [[Call]] 属性的对象返回 "function"。过去因某些浏览器曾在正则表达式中包含 [[Call]] 属性,导致正则表达式被错误鉴别为函数。

函数声明和函数表达式的一个重要区别是:函数声明会被提升至上下文(要么是该函数被声明时所在的函数范围,要么是全局范围)的顶部。

函数参数保存在类数组对象 arguments (Array.isArray(arguments) 返回 false)中。可以接收任意数量的参数。 函数的 length 属性表明其期望的参数个数。

可以像使用对象一样使用函数(因为函数本来就是对象,Function 构造函数更加容易说明)。

定义多个同名的函数时,只有最后的定义有效,之前的函数声明被完全删除(函数也是对象,变量只是存指针)。


对象某个属性的值是函数,该函数就称为方法;

所有的函数作用域内都有一个 this 对象代表调用该函数的对象。在全局作用域中,this 代表全局对象(浏览器里的 window)。当一个函数作为对象的方法调用时,默认 this 的值等于该对象。 this在函数调用时才被设置。

// 有 3 种函数方法改变 this 值。
fun.call(thisArg[, arg1[, arg2[, ...]]]);
fun.apply(thisArg, [argsArray]);
fun.bind(thisArg[, arg1[, arg2[, ...]]])
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" 因为绑定的形参,会忽略调用时再传入参数


3. 对象
对象是属性的无序集合;

在 Chrome 中,对象属性会按 ASCII 表排序,而不是定义时的顺序

判断属性是否存在的方法是使用 in 操作符。 in 操作符会检查自有属性和原型属性。

判断是否为自有属性:obj.hasOwnProperty("name")
删除属性:delete obj.xxx
判断属性是否可枚举:arr.propertyIsEnumerable("length")
所有的对象都拥有的 hasOwnProperty() 方法(其实是继承自 Object.prototype 原型对象的),该方法在给定的属性存在且为自有属性时返回 true。

删除属性:delete obj.xxx

MDN:delete 操作符不能删除的属性有:

①显式声明的全局变量不能被删除,该属性不可配置(not configurable); 

②内置对象的内置属性不能被删除; 

③不能删除一个对象从原型继承而来的属性(不过你可以从原型上直接删掉它)。

所有人为添加的属性默认都是可枚举的。可枚举的内部特征 [[Enumerable]] 都被设置为 true。对象的大部分原生方法的 [[Enumerable]] 特征都被设置为 false。可用 propertyIsEnumerable()方法检查一个属性是否为可枚举的。arr.propertyIsEnumerable("length")


属性分为 数据属性 和 访问器属性 两种类型:数据属性包含一个值,访问器属性定义了当一个属性被读取(getter)和被写入(setter)时调用的函数。

数据属性和访问器属性均由以下两个属性特制: 

[[Enumerable]] 决定了是否可以遍历该属性;

[[Configurable]] 决定了该属性是否可配置。

数据属性额外拥有两个访问器属性不具备的特征:

[[Value]] 包含属性的值(哪怕是函数)。

[[Writable]] 布尔值,指示该属性是否可写入。

访问器属性额外拥有两个特征:

[[Get]] 和 [[Set]],内含 getter 和 setter 函数。

所有人为定义的属性默认都是可枚举、可配置的。

可以用 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
})
数据属性额外拥有两个访问器属性不具备的特征:

[[Value]] 包含属性的值(哪怕是函数)。

[[Writable]] 布尔值,指示该属性是否可写入。

所有属性默认都是可写的。

enumberable、configurable 和 writable 默认均为 true
在 Object.defineProperty() 被调用时:

a. 如果属性本来就有,则会按照新定义属性特征值去覆盖默认属性特征(enumberable、configurable 和 writable 均为 true),只有你指定的特征会被改变;

b. 定义新的属性时,没有为所有的特征值指定一个值,则所有布尔值的特征值会被默认设置为 false。即不可枚举、不可配置、不可写的。 

访问器属性额外拥有两个特征:

[[Get]] 和 [[Set]],内含 getter 和 setter 函数。

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;
    }
  }
})


获取属性特征 Object.getOwnPropertyDescriptor() :

接受两个参数:对象和属性名。如果属性存在,它会返回一个属性描述对象,内涵 4 个属性:configurable 和 enumerable,另外两个属性则根据属性类型决定。

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

对象和属性一样具有指导其行为的内部特性:

 [[Extensible]] 是布尔值,指明该对象本身是否可以被修改。默认是 true。当值为 false 时,就能禁止新属性的添加。

Object.preventExtensions() :创建一个不可扩展的对象(即不能添加新属性);

Object.isExtensible() :检查 [[Extensible]] 的值。

Object.seal():创建一个不可扩展不可配置只能读写属性的对象;

Object.isSealed() :判断一个对象是否被封印。

Object.freeze() :创建一个数据属性都为只读的被封印对象(不能添加或删除属性,不能修改属性类型,也不能写入任何数据属性);

Object.isFrozen() :判断对象是否被冻结。

4. 构造函数和原型对象
每个对象都有隐式的__proto__ ([[prototype]])属性,值为创建这个对象的构造函数的原型对象;几乎所有的函数(除了一些内建函数)都有prototype属性,值为这个构造函数的原型对象。所有创建的对象实例(同一构造函数,当然,可能访问上层的原型对象)共享该原型对象,且这些对象实例可以访问原型对象的属性。

let obj = {}
obj.__proto__ === Object.prototype
构造函数的原型对象有个 constructor 属性,指向这个构造函数。

借图。实例~原型对象~构造函数
构造函数属性可以被覆盖(person.constructor = "")。

可借助 prototype 原型对象共享同一个方法会更高效:

function Person(name){
    this.name = name
}
Person.prototype.say = function(){
  console.log(this.name)
}
鉴别一个原型属性(通过继承来的属性):

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

当读取一个对象 obj 的属性时,JavaScript 引擎首先在该对象的自有属性查找属性名。如果找到则返回;否则会搜索 obj 对象的 [[Prototype]] 中的属性,找到则返回,找不到则返回undefined;(如果查找过程还没有到 Object.__proto__  则继续搜索 obj.__proto_.__proto__ ( obj的原型对象的原型对象 )中的属性,一直到 Object.__proto__为止。 Object.__proto__ === null)

person -> person.__proto__ -> person.__proto__.__proto__(Object.prototype)->null


Object.getPrototypeOf() 方法可读取 [[Prototype]] 属性的值;

isPrototypeOf() 方法会检查某个对象是否是另一个对象的原型对象:

let obj = {}
Object.getPrototypeOf(obj) === Object.prototype // true

Object.prototype.isPrototypeOf(obj) // true


直接改变构造函数的原型对象:

function Person(name){
  this.name = 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 = name
}

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

  sayName: function(){
    console.log(this.name);
  },
  toString: function(){
    return "[Person ]" + this.name + "]";
  }
}
因为每个对象的 [[Prototype]] 只是一个指向原型对象的指针,原型对象的改动会立刻反映到所有引用它的对象。 当对一个对象使用封印 Object.seal() 或冻结 Object.freeze() 时,完全是在操作对象的自有属性,仍然可以通过在原型对象上添加属性来扩展这些对象实例。

内建对象的原型对象也能修改:

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


5. 继承
JavaScript中的继承是通过原型链实现的。对象实例继承了原型对象的属性,而原型对象作为对象也有自己的原型对象,一直到Object.prototype为止。所有对象都继承自 Object.prototype。任何以对象字面量形式定义的对象,其 [[Prototype]] 的值都被设为 Object.prototype,这意味着它继承 Object.prototype 的属性。

Object.prototype 一般有以下几个方法

hasOwnProperty() 检测是否存在一个给定名字的自有属性
propertyIsemumerable() 检查一个自有属性是否可枚举
isPrototypeOf() 检查一个对象是否是另一个对象的原型对象
valueOf() 返回一个对象的值表达
toString() 返回一个对象的字符串表达
这 5 种方法经由继承出现在所有对象中。 因为所有对象都默认继承自 Object.prototype,所以改变它就会影响所有的对象。所以不建议。


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
也可以用 Object.create()创建一个 [[Prototype]] 为 null 的对象:

var obj = Object.create(null);

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值