1.对象
(1)深浅拷贝
只会拷贝对象的第一层
1.浅拷贝(4种)
(1)直接赋值
1
2
3
4
5
6
7
|
let obj = {
name: 'XKHM',
age: 25,
job: 'Software Engineer'
}
let obj1 = obj;
console.log(obj1 === obj);
|
(2)Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
语法:
Object.assign(target, …sources)
注意:第一个参数之后可以跟多个对象参数
1
2
3
4
5
6
7
8
9
10
11
|
let obj = {
name: 'XKHM',
age: 25,
job: 'Software Engineer'
}
let obj3 = {
money: 50
}
let obj1 = {};
Object.assign(obj1, obj, obj3)
console.log(obj1);
|
利用Object.assign()进行浅拷贝:
1
2
3
4
5
6
7
8
9
|
let obj = {
name: 'XKHM',
age: 25,
job: 'Software Engineer'
}
let obj1 = Object.assign({}, obj);
obj1.age = 28;
console.log(obj1);
console.log(obj);
|
(3)使用展开语法
1
2
3
4
5
6
7
8
9
|
let obj = {
name: 'XKHM',
age: 25,
job: 'Software Engineer'
}
let obj1 = { ...obj };
obj1.age = 28;
console.log(obj1);
console.log(obj);
|
(4)循环遍历赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
let obj = {
name: 'XKHM',
age: 25,
job: 'Software Engineer'
}
let obj1 = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
obj1[key] = obj[key];
}
}
obj1.age = 28;
console.log(obj1);
console.log(obj);
|
拓展:遍历对象的几种方式
- 1.for…in 会遍历继承到的属性,注意判断
- 2.Object.keys(obj),Object.values(obj),Object.entries(obj)
- 3.Object.getOwnPropertyNames()
- 4.Reflect.ownKeys
- 5.借用展开语法
2.深拷贝
拷贝一个一摸一样的新对象,不共享内存,修改一个对象不会影响到另一个对象
(1)JSON.parse(JSON.stringify())
1
2
3
4
5
6
7
8
9
10
11
12
|
let obj = {
name: {
firstName: '星空',
lastName: '海绵'
},
age: 25,
job: 'Software Engineer'
}
let obj1 = JSON.parse(JSON.stringify(obj));
obj1.name.firstName = '小小'
console.log(obj1);
console.log(obj);
|
缺陷:这个方法不能处理function和undefined、Symbol,会直接忽略
(2)递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// 深拷贝 递归
let obj = {
name: {
firstName: '海绵',
lastName: '星空',
fn: function () { },
un: undefined,
nu: null
},
fn: function () { },
un: undefined,
nu: null
}
function deepObject(obj) {
let obj1 = Array.isArray(obj) ? [] : {};
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
obj1[key] = deepObject(obj[key]);
} else {
obj1[key] = obj[key];
}
}
return obj1;
}
// 测试
let obj2 = deepObject(obj);
obj2.name.firstName = '小星星';
console.log(obj2);
console.log(obj);
|
(3)Jquery的$extend()
(4)lodash的_.defaultesDeep()
3.解决循环引用问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
let obj = {
a: 10
}
// 循环引用
obj.b = obj;
function deepObject(obj) {
console.log(123);
let obj1 = Array.isArray(obj) ? [] : {};
for (const key of Object.keys(obj)) {
if (typeof obj[key] === 'object' &&
obj[key] !== null && obj[key] !== obj) {
obj1[key] = deepObject(obj[key]);
} else {
obj1[key] = obj[key];
}
}
return obj1;
}
// 测试
let obj2 = deepObject(obj);
obj2.a = 100;
console.log(obj2);
console.log(obj);
|
在拿到对象属性值的时候做个判断,看是不是等于传进来的对象,如果是就不进行递归,如果进行递归的话会爆栈。
4.总结
(2)new
1.手写一个new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function A() {
this.name = '星空海绵'
}
function myNew(fn, ...rest) {
let obj = Object.create(fn.prototype);
let res = fn.apply(obj, rest);
return res instanceof Object ? res : obj
}
// 测试
let a = new A('145');
console.log(a);
let a1 = myNew(A, '145');
console.log(a1);
|
2.new创建对象的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function A() {
return null
}
function myNew(fn, ...rest) {
let obj = {};
obj.__proto__ = fn.prototype;
let res = fn.apply(obj, rest);
return res instanceof Object ? res : obj
}
// 测试
let a = new A('145');
console.log(a);
let a1 = myNew(A, '145');
console.log(a1);
|
不传参写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function A() {
this.name = '星空海绵'
}
function myNew() {
let arg = arguments[0],
args = [...arguments].slice(1);
let obj = Object.create(arg.prototype);
let res = arg.apply(obj, args);
return res instanceof Object ? res : obj;
}
// 测试
let a = new A('145');
console.log(a);
let a1 = myNew(A, '145');
console.log(a1);
|
3.手写instanceof
instanceof运算符用来检测构造函数的prototype(原型)是否出现在某个实例对象的原型链上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function Right() { }
let left = new Object(Right);
console.log(left instanceof Right);
console.log(myInstanceof(left, Right));
function myInstanceof(left, Right) {
left = left.__proto__;
Right = Right.prototype;
while (true) {
if (left === null) {
return false
}
if (left === Right) {
return true
}
left = left.__proto__;
}
}
|
原型链中的最后一个环节为null,一般情况下倒数第二个环节为Object.prototype
(3)创造对象的方式
1.字面量的形式
缺点:只能创建一次对象,复用性较差
2.new Object构造函数
3.调用Object.create()
1
|
let obj = Object.create()
|
(4)JS继承的方式
JS中的继承都是实现继承。
1.原型链继承
1
2
3
4
5
6
7
8
9
|
function A() {
this.prototype = true
}
function B() {
this.selfPrototype = false
}
B.prototype = new A();
let instance = new B();
console.log(instance.prototype);
|
缺点:
-
1.原型中包含的引用值会在实例间共享
-
2.子类型在实例化时不能给夫类型的构造函数传参
1
2
3
4
5
6
7
8
9
10
11
|
function A() {
this.prototype = ['星空', '海绵', '掘金', '思否']
}
function B() {
this.selfPrototype = false
}
B.prototype = new A();
let instance = new B();
let instance1 = new B();
instance.prototype[0] = '阿星';
console.log(instance1.prototype); // ["阿星", "海绵", "掘金", "思否"]
|
上面的例子中,我们改变实例instance会导致实例instance1上继承的prototype属性值也改变了,这就是原型中包含的引用值会在实例间共享。
思考:那怎么能让它不共享呢
2.盗用构造函数
盗用构造函数继承也被称为经典继承
1
2
3
4
5
6
7
8
9
10
|
function A() {
this.showName = ['星空', '海绵', '掘金', '思否']
}
function B() {
A.call(this)
}
let instance = new B();
let instance1 = new B();
instance.showName[0] = '阿星';
console.log(instance1.showName); // ["星空", "海绵", "掘金", "思否"]
|
通过上面这种方式创建的实例都会有自己的showName,原型上的引用值不会在实例间共享。
同时,盗用构造函数也能传参:
1
2
3
4
5
6
7
8
9
10
|
function A(showName) {
this.showName = showName;
}
function B(name) {
A.call(this, name)
}
let instance = new B('星空');
let instance1 = new B('海绵');
console.log(instance.showName); // 星空
console.log(instance1.showName); // 海绵
|
这样,盗用构造函数就同时解决了上面原型链继承的缺点,但是盗用构造函数本身也有缺点。
缺点:子类不能访问父类原型上定义的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function A(showName) {
this.showName = showName;
}
A.prototype.getName = function () {
return this.showName
}
function B(name) {
A.call(this, name)
}
let instance = new B('星空');
let instance1 = new B('海绵');
console.log(instance.getName());
// instance.getName is not a function
|
3.组合继承
结合原型链继承和盗用构造函数继承组成组合继承,也就是使用原型链继承原型上的属性和方法,使用盗用构造函数继承实例本身的属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function A(showName) {
this.showName = showName;
}
A.prototype.getName = function () {
return this.showName
}
function B(name) {
A.call(this, name)
}
B.prototype = new A();
let instance = new B('星空');
let instance1 = new B('海绵');
console.log(instance.getName()); // 星空
console.log(instance1.getName()); // 海绵
|
缺点:存在效率问题,父类构造函数始终会被调用两次。
4.原型式继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function A(showName) {
this.showName = showName;
}
let instance = Object.create(A, {
showName: {
value: '星空'
}
})
let instance1 = Object.create(A, {
showName: {
value: '海绵'
}
})
console.log(instance.showName); // 星空
console.log(instance1.showName); // 海绵
|
缺点:原型式继承和原型链继承缺点一样,原型中包含的引用值会在实例间共享。
优点:适合不需要单独创建构造函数,但是需要对象间共享信息的场景。
5.寄生式继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
function object(o) {
function F() { };
F.prototype = o;
return new F();
}
function createAnother(original) {
let clone = object(original);
clone.sayHi = function () {
console.log('hi');
}
return clone
}
let person = {
name: 'Nicholas',
friends: ['星空', '海绵']
}
let anotherPerson = createAnother(person);
let anotherPerson1 = createAnother(person);
anotherPerson.friends[0] = '阿星';
anotherPerson.sayHi();
console.log(anotherPerson);
console.log(anotherPerson1);
|
缺点:寄生式继承给对象添加函数导致函数难以重用。
6.寄生式组合继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
function object(o) {
function F() { };
F.prototype = o;
return new F();
}
function inheritPrototype(B, A) {
let prototype = object(A.prototype);
prototype.constructor = B;
B.prototype = prototype;
}
function A(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
A.prototype.sayName = function () {
console.log(this.name);
}
function B(name, age) {
A.call(this, name);
this.age = age;
}
inheritPrototype(B, A);
B.prototype.sayAge = function () {
console.log(this.age);
}
|
寄生式组合继承是引用类型继承的最佳模式