写在前面
本文章为对书《JavaScript面向对象精要》的读后总结。在阅读本文章前,我们先来整理一份思维导图,帮助我们快速浏览本文大概内容
第一章 原始类型和引用类型
1.1 类型的定义
在JavaScript中,存在两种类型
- 原始类型(保存为简单数据值)
- 引用类型(保存为对象,本质是指向内存位置的引用)
1.2 原始类型
1.2.1 关于原始类型的简介
在JavaScript中,共有五种原始类型
- boolean:布尔值,值为true、false
- number:数字,值为所有的整型、浮点数值
- string:字符串,用单引号和双引号包住的字符
- null:空类型,值仅有null
- undefined:未定义,值仅有undefiend
<!--boolean-->
var isEnv = true;
var isEnv = false;
<!--number-->
var num = 1;
var cost = 1.2;
<!--string-->
var str = '我是字符串';
var name = "我也睡字符串";
<!--null-->
var obj = null;
<!--undefined-->
var res = undefined;
var resg;
在JavaScript中,一个变量等于另一个变量时,当改变其中一个变量的值,并不会影响另一个值,请看下面的例子
变量 | 值 |
---|---|
num1 | 1 |
num2 | 1 |
var num1 = 1;
var num2 = num1;
num1 = 3;
console.log('=>结果:', num1, num2); // =>结果: 3, 1
1.2.2 原始类型的鉴别
对于鉴别原始类型的最佳方式是使用typeof
操作符,例:
console.log(typeof 1); // number
console.log(typeof 1.2); // number
console.log(typeof 'typeof'); // string
console.log(typeof true); // boolean
console.log(typeof false); // boolean
console.log(typeof undefined); // undefined
关于对null
的鉴别却有不同之处
console.log(typeof null); // "object"
这里对于null
的判断,最好的方法是值直接与其进行判断
let res = null;
console.log(null === res); // true
res = undefined;
console.log(null === res); // false
console.log(null == res); // true
用==
时,两个值进行比较时会将变量强制转换为另一种类型,所以对null
和undefined
进行比较时,会出现结果为true
的情况。
1.2.3 原始方法
原始类型中除了null
、undefined
都有着原始方法
var name = "lily";
var newName = name.charAt(0);
console.log(newName); // l
var count = 10;
var newCount = count.toFixed(2);
console.log(newCount); // 10.00
1.3 引用类型
引用类型就是JavaScript
中的对象,它是最接近类的东西。
在JavaScript
中,可以利用new
操作符创建一个对象
const obj = new Object();
对于对象的解除,最佳方法是将其值变为null
let obj = new Object();
obj = null;
对象可以随时添加属性和删除属性
const obj = new Object();
obj.name = '小明';
obj.job = 'web前端开发';
console.log(obj); // {"name":"小明","job":"web前端开发"}
delete obj.name;
console.log(obj); // {"job":"web前端开发"}
还可以实例化内建类型
const array = new Array(); // 数组
const date = new Date(); // 日期
const error = new Error(); // 错误
const func = new Function(); // 函数
const reg = new RegExp(); // 正则
我们依然可以使用字面形式创建
const obj = {}; // 对象
const arr = []; // 数组
function func() { // 函数
}
const reg = /\d+/g; // 正则
1.4 访问对象的属性
在JavaScript中,访问对象的属性有两种方法
- "."的形式
- "[]"的形式
const obj = {
name: '小明',
job: 'web前端开发',
}
console.log(obj.name, obj.job); // 小明,web前端开发
console.log(obj["name"], obj["job"]); // 小明,web前端开发
1.5 鉴别引用类型
对于引用类型的鉴别,function
类型可以使用typeof
和instanceof
方法,其余的类型要用instanceof
// function
function func() {
return 1;
}
console.log(typeof func); // function
// Array
const array = [];
console.log(array instanceof Array); // true
// Object
const obj = {};
console.log(obj instanceof Object); // true
console.log(obj instanceof Array); // false
console.log(array instanceof Object); // true
// function
console.log(func instanceof Function);
console.log(func instanceof Object); // true
1.6 鉴别数组
鉴别数组可用Array.isArray()
方法
const array = [];
console.log(Array.isArray(array)); // true
1.7 原始封装类型
原始封装类型有三种:String
、Number
、Boolean
第二章 函数
2.1 函数的介绍
函数其实就是对象,函数具有两种字面形式
- 函数声明(可提升至上下文)
func(1);
function func(data) {
console.log(data);
}
- 函数表达式(不可提升)
func(1);
const func = function(data) {
console.log(data);
}
函数也是值,可以将它们赋给变量使用。
2.2 函数的参数
函数可以接收参数,我们可以给函数传递参数
函数可以接收的参数,可以使用arguments
类数组对象接收
function func() {
console.log(arguments[0]); // 1
console.log(arguments.length); // 5
}
func(1, 2, 3, 4, 5);
函数还可以正常接收参数,然后对参数进行处理
function func(a, b) {
return a + b;
}
console.log(func(1, 2)); // 3
2.3 函数的重载
JavaScript
语言根据实际传入的采纳数决定调用函数的哪个版本,JS
的函数不存在函数重载
function func(data) {
return data;
}
function func() {
return 'chongzai'
}
console.log(func('hanshu')); // 'chongzai'
但是,我们依然可以用JS
去模拟函数重载
function func(data) {
if(arguments.length === 0) {
data = 1;
}
return data;
}
console.log(func(2)); // 2
2.4 对象方法
如果属性的值是函数,那么这个属性就称为方法
var person = {
name: '小明',
sayName: function() {
return this.name;
}
}
console.log(person.sayName()); // "小明"
2.4.1 this对象
JavaScript
所有的函数作用域内都有一个this
对象代表调用该函数的对象。在全局作用域中,this
代表全局对象(浏览器中的window
)
上例中,我们就用到了this
方法,我们可以再写一个小例子理解一下this
var name = '熊大';
var person = {
name: "熊二",
sayName: function() {
return this.name;
}
}
console.log(person.sayName()); // "熊二"
var sayHello = person.sayName;
console.log(sayHello()); // "熊大"
2.4.2 改变this指向
1、call()
call()
方法的第一个参数是this
所指向的值,后边的参数就是需要传入函数的参数
var name = "光头强";
var person1 = {
name: "熊大"
}
var person2 = {
name: "熊二"
}
function sayName(type) {
return `${this.name}是${type}`
}
console.log(sayName.call(person1, '好人')) // 熊大是好人
console.log(sayName.call(person2, '好人')) // 熊二是好人
console.log(sayName.call(this, '坏人')) // 光头强是坏人
2、apply()
apply()
方法和call()
方法的区别就是:apply()
第二个参数是传给函数的参数,它是一个数组
var name = "光头强";
var person1 = {
name: "熊大"
}
var person2 = {
name: "熊二"
}
function sayName(type) {
return `${this.name}是${type}`
}
console.log(sayName.call(person1, ['好人'])) // 熊大是好人
console.log(sayName.call(person2, ['好人'])) // 熊二是好人
console.log(sayName.call(this, ['坏人'])) // 光头强是坏人
3、bind()
bind()
的第一个参数是要传给新函数的this
的值,其他所有参数代表需要被永久设置在新函数中的命名参数
var name = "光头强";
var person1 = {
name: "熊大"
}
var person2 = {
name: "熊二"
}
function sayName(type) {
return `${this.name}是${type}`
}
var sayName1 = sayName.bind(person1);
console.log(sayName1('好人')); // 熊大是好人
var sayName2 = sayName.bind(person2);
console.log(sayName2('好人')); // 熊二是好人
person2.sayName = sayName1;
console.log(person2.sayName("好人")); // 熊大是好人
第三章 理解对象
3.1 定义属性
// 第一种方式,创建对象时直接定义
var person = {
name: '熊大',
}
// 第二种方式,创建对象之后定义(以下两种创建对象方式等价)
var person1 = {};
var person1 = new Object();
person.age = 22;
person1.name = '熊大';
person1.age = 24;
person1.age = 25;
console.log(person); // {"name": "熊大", "age": 22}
console.log(person1); // {"name": "熊大", "age": 25}
定义一个对象中没有的属性的时候,JS
会调用[[put]]
方法,当为对象中已经拥有的属性赋新值的时候,JS
会调用[[set]]
方法
3.2 属性探测
属性探测的三种方式
if
判断
var person = {name: '熊大', age: 0};
if(person.name) {
console.log('找到啦');
}
这种方式存在的问题:当if
中判断的值是对象、非空字符串、非零数字或true
时,返回true
,当判断的值是null
、undefined
、0
、false
、NaN
或空字符串时会返回false
。但是对象中可包括价值,比如例子中,age
的属性值为0
,那么判断就会返回false
,即使包含了age
属性,也不会检测到,所以这种探测属性的方式并不推荐
in
操作符
var person = {
name: '熊大',
age: 0,
sayName: function() {
return this.name + this.age;
}
};
console.log("name" in person); // true
console.log("age" in person); // true
console.log("job" in person); // false
console.log("sayName" in person); // true
console.log("toString" in person); // true
这种方式存在的问题:in
操作符会检查自由属性和原型属性,可大多数时候我们可能只是想探测自由属性是否存在。所以我们会使用所有对象都拥有的hasOwnProperty()
方法探测
hasOwnProperty
var person = {
name: '熊大',
age: 0,
sayName: function() {
return this.name + this.age;
}
};
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("age")); // true
console.log(person.hasOwnProperty("job")); // false
console.log(person.hasOwnProperty("sayName")); // true
console.log(person.hasOwnProperty("toString")); // false
3.3 删除属性
设置一个属性的值为null
,并不能完全删除属性,它只是将一个属性赋值为了null
,我们需要用delete
去操作。
var person = {
name: '熊大',
age: 11,
sayName: function() {
return this.name + this.age;
}
}
delete person.name;
delete person.sayName;
console.log(person); // {age: 11}
3.4 属性枚举
我们自己添加的属性默认是可枚举的,枚举属性的内部特征[[Enumerable]]
为true
。我们有两种方式可枚举属性:
- for-in[同时遍历自有属性和原型属性]
var person = {
name: '熊大',
age: 11,
}
for(var key in person) {
console.log(key, person[key]); // name, '熊大' age, 11
}
- Object.keys()[只遍历自有属性]
var object = Object.keys(person);
for(var i = 0;i < object.length;i++) {
var key = object[i];
console.log(key, person[key]); // name, '熊大' age, 11
}
我们要注意,不是所有的属性都是可枚举的,对象的大部分原生方法的[[Enumerable]]
特征都被设置为false
,我们可以用propertyIsEnumerable()
方法检测该属性是否可枚举
var person = {
name: '熊大'
}
console.log(person.propertyIsEnumerable("name")); // true
console.log(person.propertyIsEnumerable("toString")); // false
3.5 属性类型
- 数据属性:包含一个值
- 访问器属性:读取函数
getter
和属性写入函数setter
var person = {
_name: '熊大',
get name() {
console.log('get');
return this._name;
},
set name(value) {
console.log('set');
this._name = value;
}
}
console.log(person.name);
person.name = '熊二';
console.log(person);
从输出的结果看,我们取name
值的时候用到了get
方法,设置name
值的时候,用到了set
方法。
3.6 属性特征
3.6.1 通用特征
[[Enumerable]]
:决定是否可以遍历属性[[Configurable]]
:决定了该属性是否可配置
var person = {
name: '熊大',
}
Object.defineProperty(person, "name", { // 设置name属性不可遍历
enumerable: false
})
console.log(person.hasOwnProperty("name")); // true
console.log(person.propertyIsEnumerable("name")); // false
Object.defineProperty(person, "name", { // 设置name属性不可配置
configurable: false
})
delete person.name;
console.log(person.name);
Object.defineProperty(person, "name", { // Uncaught TypeError: Cannot redefine property: name
configurable: true
})
3.6.2 数据属性特征
[[Value]]
:属性的值[[Writable]]
:属性是否可写入
var person = {}
Object.defineProperty(person, "name", {
enumerable: true,
configurable: true,
value: '熊大',
writable: true,
})
console.log(person); // {name: '熊大'}
调用Object.defineProperty()
时,会先检查属性是否存在,当不存在时,会根据我们指定的特征创建这个属性。我们在指定特征的时候要注意:如果不给属性指定特征,那么这个属性默认的特征的false
var person = {};
Object.defineProperty(person, "name", {
value: '熊大'
})
console.log(person.name); // 熊大
console.log(person.propertyIsEnumerable("name")); // false
delete person.name;
console.log(person.hasOwnProperty("name")); // true
person.name = '熊二';
console.log(person.name); // 熊大
上例中创建的name
属性就是不可遍历、不可配置、不可写入的。
3.6.3 访问器属性特征
[[get]]
:取某个属性的值[[set]]
:给属性赋值
不能创建一个同时具有数据特征和访问器特征的属性
var person = {
_name: '熊大'
}
Object.defineProperty(person, "name", {
get: function() {
console.log('get');
return this._name;
},
set: function(value) {
console.log('set');
this._name = value;
},
enumerable: true,
configurable: true,
})
console.log(person.propertyIsEnumerable("name")); // true
console.log(person.name); // get 熊大
person.name = '熊二'; // set
console.log(person.name); // get 熊二
delete person.name;
console.log(person.name); // undefined
3.6.4 定义多重属性
使用Object.defineProperties()
可以为一个对象定义多个属性。
var person = {};
Object.defineProperties(person, {
name: {
value: "熊大",
enumerable: true,
confogurable: true,
writable: true,
},
age: {
value: 11,
enumerable: true,
confogurable: true,
writable: true,
}
})
console.log(person); // {name: '熊大', age: 11}
console.log(person.name); // 熊大
console.log(person.age); // 11
3.6.5 获取属性的特征
我们定义好的属性特征,还可以用Object.getOwnPropertyDescriptor()
方法获取
var person = {name: '熊大'}
var descriptor = Object.getOwnPropertyDescriptor(person, "name");
var { enumerable, configurable, writable, value } = descriptor;
console.log(enumerable, configurable, writable, value); // true true true '熊大'
3.7 禁止修改对象
[[Extensible]]
表示该属性是否可被修改
3.7.1 禁止扩展
用Object.preventExtensions()
方法创建一个不可扩展的对象,这就不可以为对象新增属性了。用Object.isExtensible()
方法检查[[Extensible]]
的值
var person = {name: '熊大'};
console.log(Object.isExtensible(person)); // true
Object.preventExtensions(person)
person.age = 11;
console.log(person); // {name: '熊大'}
console.log(person.age); // undefined
console.log(Object.isExtensible(person)); // false
3.7.2 对象封印
对象封印也可以创建一个不可扩展的对象,被封印的对象不可扩展并且所有属性都不可配置。对象封印使对象不能添加属性,也不能删除、改变类型(只能读写)
Object.seal()
方法封印对象,Object.isSealed()
判断一个对象是否被封印
var person = {
name: '熊大'
}
console.log(Object.isSealed(person)); // false
Object.seal(person);
person.age = 11;
person.name = '熊二';
console.log(person.name); // 熊二
delete person.name;
console.log(Object.isSealed(person)); // true
console.log(person); // {name: '熊二'}
3.7.3 对象冻结
对象冻结也是扩展对象的一种方法,一个被冻结的对象只为可读,被冻结的对象无法解冻,用Object.freeze()
方法冻结一个对象,用Object.isFrozen()
来判断对象是否冻结
var person = {
name: '熊大'
}
console.log(Object.isFrozen(person)); // false
Object.freeze(person);
console.log(Object.isFrozen(person)); // true
person.age = 11;
person.name = '熊二';
delete person.name;
console.log(person); // {name: '熊大'}
总结
本文主要整理了《JavaScript面向对象精要》中的前三章内容,如有不对之处,还请大家指出~
下一篇文章将会对本书的后三章进行整理,目录如下:
- 第四章 构造函数和原型对象
- 第五章 继承
- 第六章 对象模式
最后,分享一波我的个人公众号「web前端日记」,欢迎大家扫描下方的二维码前来关注~