js的继承与原型链污染

前言

迷迷糊糊终于搞懂了js的面向对象(大概),这里简单记个笔记。

javascipt中的类

众所周知,javascript虽然可以进行面向对象编程,但其本身是没有class这一概念的。javascript使用function来实现class这一概念。

1
2
3
4
5
6
function o1() {
this.test = "aaa";
}

let a = new o1;
console.log(a.test); //aaa

即使是有class这一关键词,也只是一个语法糖,其本质还是function

1
2
3
4
5
6
7
class o1 {
constructor() {
this.test = "aaa";
}
};

console.log(typeof o1); //function

继承

js的继承有多种方法实现,这里只介绍组合继承

原型链

对于一个类,它有自己的原型对象Class.prototype,通过prototype关键字访问
对于一个类的实例(对象),它可以通过__proto__关键词访问对应的原型对象
而关键词constructor是上面两个的逆过程
一个原型对象也可以通过__proto__访问父级原型对象
总结:
prototype 类的关键词,访问对应原型对象
__proto__ 对象的关键词,访问对应原型对象
constructor 对象的关键词,访问对应的类

下例是一个继承链和构造函数的组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function o1(id) {
this.id = id;
};
o1.prototype.sayId = function() {
console.log(this.id);
}

function o2(id, name) {
o1.call(this, id);
this.name = name;
}
o2.prototype = new o1();
o2.prototype.sayName = function() {
console.log(this.name);
}
o2.prototype.constructor = o2;

o2.prototype.constructor = o2 ???

请看下例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function o1() {
this.o1 = 1;
}

function o2() {
o1.call(this);
this.o2 = 2;
}
o2.prototype = new o1;

function o3() {
o2.call(this);
this.o3 = 3;
}
o3.prototype = new o2;

let test = new o3;
console.log(test.constructor == o3);

请猜猜最后输出什么?
最开始我认为对象的constructor应该指向自己的构造函数,所以应该是true?
但这是不对的,是false!
construstor属性也是可以继承的,这样一层一层继承导致o3继承了o1的construstor
我这样写继承是错误的,正确的方法应该是这样的,将原型的construstor重新指向自己的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function o1() {
this.o1 = 1;
}


function o2() {
o1.call(this);
this.o2 = 2;
}

o2.prototype = new o1;
o2.prototype.constructor = o2;

function o3() {
o2.call(this);
this.o3 = 3;
}

o3.prototype = new o2;
o3.prototype.constructor = o3;

let test = new o3;
console.log(test.constructor == o1); //false
console.log(test.constructor == o3); //true

成员属性

private

对于private属性的成员来说来说,它只可以在构造函数内部使用的时候进行访问。
根据let关键词的特性(不会变量提升,可以只在块级作用域作用等),它可以作为private使用。
同时,对于私有的方法,可以直接套娃实现。这样的函数无法被实例化的对象调用,也不会被继承。
如果它可以被外部访问,那它准确来说是一个protected属性。

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
function TheFather() {
let a = 1;

function private_1() {
console.log("I am private!");
return a;
}
}

function son1() {
let b = 2;

function private_1() {
console.log("I am private!");
return b;
}
}
son1.prototype = new TheFather;
son1.prototype.constructor = son1;

let test1 = new TheFather;
//console.log(test1.a); //undefined
//console.log(test1.private_1()); //error

let test2 = new son1;
//console.log(test2.a); //undefined
//console.log(test2.private_1()); //error
//console.log(test2.private_2()); //error

public

对于public对象,它是可以被外部访问,也可以被继承的。
我们只需要用this就可以了

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
31
32
function TheFather() {
this.a = 1;

this.public_1 = function() {
return this.a;
}
}
TheFather.prototype.public_2 = function() {
return this.a;
}

function son1() {
this.b = 2;

this.public_3 = function() {
return this.b;
}
}
son1.prototype = new TheFather;
son1.prototype.constructor = son1;

let test1 = new TheFather;
//console.log(test1.a); //1
//console.log(test1.public_1()); //1
//console.log(test1.public_2()); //1

let test2 = new son1;
//console.log(test2.a); //1
//console.log(test2.b); //2
//console.log(test2.public_1()); //1
//console.log(test2.public_2()); //1
//console.log(test2.public_3()); //2

prototype

对于prototype,我们要让只有实例化的对象和子类的对象可以访问它。
我们没有具体实现的方法,可以将private属性变得可以访问即可。可以使用一个公用函数访问它。

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
function TheFather() {
let a = 1;

this.private_1 = function() {
return a;
}
}

function son1() {
let b = 2;

this.private_2 = function() {
return b;
}
}
son1.prototype = new TheFather;
son1.prototype.constructor = son1;

let test1 = new TheFather;
//console.log(test1.a); //undefined
//console.log(test1.private_1()); //1

let test2 = new son1;
//console.log(test2.a); //undefined
//console.log(test2.private_1()); //error
//console.log(test2.private_2()); //error

原型链污染

因为js的对象继承是基于原型链的,所以如果把上一层的原型污染了就会导致下层的属性被污染。
举个例子

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//判断是否为一个对象
function isObject(a) {
if(typeof(a) == "object") {
return true;
} else {
return false;
}
}

//对象复制
function merge(a, b) {
for (var attr in b) {
if(isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}

//user父级对象,有一个判断是否为admin的属性
function user() {
this.IsAdmin = false;
};

//游客对象,继承user,IsAdmin默认为False
function guest() {
this.check = function() {
console.log("I am guest");
};
};
guest.prototype = new user;

//管理员对象,继承user,IsAdmin修改为True
function admin() {
this.check = function() {
console.log("I am admin");
};
};
admin.prototype = new user;
admin.prototype.IsAdmin = true;

let P1 = new guest;
let P2 = new admin;

console.log(P1.IsAdmin); //false
console.log(P2.IsAdmin); //true

//假设这里可以控制json,提交以下数据污染原型user
let o1 = JSON.parse('{"__proto__":{"IsAdmin":true}}');
merge(P1, o1);
console.log(P1.IsAdmin); //true

Comments