文章目录
- 创建对象
- 克隆
- 原型对象、构造函数、实例
- 原型链
什么是对象呢?
对象是无序属性的集合,这些属性可以包括基本数据类型、对象或者函数,总之对象就是一组键值对的集合。
对象属性的访问可以通过点操作符和中括号操作符
- 点操作符,是最常见的一种方法
- []访问属性,语法:
object[propertyName]
点操作符是静态的,只能用属性名称命名的简单描述,而且是不能修改的而且操作符不能用数字作为属性名;中括号操作符是动态,可以传递字符串或者变量。
如果对象属性中出现导致语法错误的字符,那就只能使用中括号操作符来访问该属性,而不能使用点操作符。
创建对象
一、通过构造函数Object()来创建一个对象,然后再添加需要的属性,比如:
const person = new Object();
person.name = 'duxin';
person.age = 11 ;
person.getName = function(){
return this.name;
}
二、基于对象的字面量,声明对象,比如:
var person = {
name: 'kingx',
age: 11,
getName: function () {
return this.name;
},
address: {
name: '北京市',
code: '100000'
}
};
三、基于工厂模式,这种方式是对外暴露需要设置的属性值,比如:
// 工厂方法,对外暴露接收的name、age、address属性值
function createPerson(name, age,) {
// 内部通过Object()构造函数生成一个对象,并添加各种属性
var o = new Object();
o.name = name;
o.age = age;
o.address = address;
o.getName = function () {
return this.name; };
// 返回创建的对象
return o;
}
var person = createPerson('kingx', 11, {
name: '北京市',
code: '100000'
});
工厂模式可以减少很多重复的代码,比如上面的代码中,如果多个对象,都是含有name、age这些属性,那么单独创建每一个对象的话,会出现很多的重复代码。
四、基于原型对象,是把所有的属性和函数都封装到对象的prototype属性上,代码如下:
function Person(){}
Person.prototype.name = 'name';
Person.prototype.age = 90;
const person = new Person()
console.log(person.name); // name
克隆
对于基本数据类型,变量存储的是值本身,可以直接访问;
对于引用数据类型,变量储存的是值在内存中的地址,这个地址是指向内存中的某个位置,如果多个变量同时指向同一个内存地址,那么其中一个变量对值进行修改,就会影响到其他变量。
所以在我们对一个对象变量进行修改的时候, 需要克隆出来在进行修改。以下是几个对象克隆的方法:
- Object.assign()函数,直接将源对象的可枚举属性直接复制到目标对象中,如果原始对象是引用类型的值,那么对克隆对象的值进行修改,也会影响到原始对象,这就是浅克隆的缺点。
- JSON序列化和反序列化,可以深度克隆。JSON序列化可以解决大部分对象的深度克隆,但是会存在几个问题:
- JSON序列化和反序列化不能对函数、正则这个特殊的对象进行深度克隆
- 对象的constructor会被抛弃,所有的构造函数指向Object,原型链关系断裂
- 如果对象中存在循环引用,就会抛出异常。
- 自定义深度克隆,代码如下:
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date)
return new Date(obj) // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp)
return new RegExp(obj) //正则对象直接返回一个新的正则对象
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
//继承原型链
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}
其他工具也可以实现对象深度克隆,比如lodash。
原型对象、构造函数、实例
函数在创建后,都会有一个属性prototype,它指向函数的原型对象,这个原型对象是包含了所有实例共同的属性和函数。
原型对象有一个constructor属性,是指向原型对象所在的构造函数。当我们通过new操作符调用构造函数来创建一个实例的时候,实例具有一个__proto__属性,指向的是构造函数的原型对象。
比如有一个构造函数Person:
function Person(){}
Person.prototype.name = 'duxin';
const person = new Person();
构造函数有一个prototype属性,指向Person的原型对象,原型对象上的constructor属性指向的是构造函数本身:
console.log(Person.prototype);// { name: 'duxin' }
console.log(Person.prototype.constructor) ;// [Function: Person]
通过new生成的实例,其中实例的proto属性指向原型对象:
function Person(){}
Person.prototype.name = 'duxin';
const person = new Person();
console.log(Person.prototype);// { name: 'duxin' }
console.log(Person.prototype.constructor) ;// [Function: Person]
const person1 = new Person();
console.log(person.__proto__);//{ name: 'duxin' }
console.log(person1.__proto__);//{ name: 'duxin' }
那么实例的属性是怎么读取的呢?
在读取实例对象的某个属性的时候,首先的在实例本身查找指定属性,如果找不到,就从原型对象上查询,比如:
person.age = 90
console.log(person.name,person)
person对象生成之后,只添加了age属性,person自身存在name属性的,在读取的时候,在对象自身没有找,就往原型对象上查找。
这三个的关系如下图:
原型链
实例对象都有__proto__属性,指向的是构造函数的原型对象,那么同样原型对象也存在一个__proto__属性,指向的是上一级构造函数的原型对象,这样层层往上,最上层的原型对象为null。
基本上所有的对象都具有__proto__属性,通过__proto__属性连接形成的链路,这就是JavaScript的原型链。原型链的顶端是Object.prototype,它的__proto__属性为null,代码如下:
const obj = {
name:"duxin"
}
console.log(obj.__proto__) //[Object: null prototype] {}
原型链的特点:
- 在查找属性的时候,是查询整个原型链的,如果找不到,就返回undefined;
- 通过hasOwnProperty函数来判断一个属性是否是自身属性;
- 内置的构造函数【比如String()、Number()】的__proto__属性是指向Function.prototype。