JS对象及继承方式综述
JS对象知识回顾
-
JS对象是若干无序属性的集合(数据属性、访问器属性、内部属性)
-
生成对象的3种方式:字面量直接生成、Object工场方法、构造函数实例化对象
在上述的过程中,有一个Person.protorype.sayHi是给原型添加sayHi方法。
注意:create方法添加属性
1
2
3
4
5
6
7
8
9
10 1var empty = {};
2var obj2 = Object.create(empty, {
3 x: { value: 1 },
4 y: { value: 2, enumerable: true }
5});
6console.log(obj2);
7//返回的是自身的属性, 包括不可枚举属性, 但是会先返回可枚举属性, 之后返回不可枚举属性, 但是不可以返回继承的属性
8console.log(obj2.hasOwnProperty("x"));// true
9
10
其中的console.log(obj2),返回的是自身的属性,包括不可枚举属性,但是会先返回可枚举属性,之后返回不可枚举属性,但是不可以返回继承的属性。
JavaScript语言继承方式
-
JavaScript采用的是原型的继承方式,每个对象都有一个原型对象,最原始的原型是null
-
JavaScript的继承是对象-对象的原型继承,为面向对象提供了动态继承的功能
-
任何方式创建的对象都有原型对象,可以通过对象的 __proto__属性来访问原型对象
1
2
3
4
5
6
7
8
9
10
11
12
13 1var obj = { num: 10 };
2console.log(obj.__proto__ === Object.prototype); // true
3var newObj = Object.create(obj);
4var newObj2 = Object.create(obj); //多个对象同一个原型的情况
5newObj.age = 23;
6console.log(newObj.__proto__ === obj); // true
7console.log(newObj2.__proto__ === obj); // true
8
9// 思考
10console.log(newObj.__proto__.__proto__); //Object.prototype
11console.log(newObj.__proto__.__proto__.__proto__); //null
12
13
上述的关系可以用以下图示来反映:
JS对象的原型链
1
2
3
4
5
6
7
8
9
10
11
12 1/*JS对象的原型链*/
2var proObj = { z: 3 };
3var obj = Object.create(proObj);
4obj.x = 1;
5obj.y = 2;
6console.log(obj.x); //1
7console.log(obj.y); //2
8console.log(obj.z); //3
9console.log("z" in obj); //true in可以从原型链上进行查找
10console.log(obj.hasOwnProperty("z")); //false hasOwnProperty不包括继承下来的属性
11
12
上述的代码可以变换成以下图示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1obj.z = 5;
2console.log(obj.hasOwnProperty("z")); // true
3console.log(obj.z); // 5
4console.log(proObj.z); ///3
5obj.z = 8;
6console.log(obj.z); //8
7delete obj.z; //true
8console.log(obj.z); //3
9delete obj.z; //true 此时会静默失败,由于此时obj本身没有z这个属性,因此将不会删除z
10console.log(obj.z); //still 3
11//如何删除原型上的属性
12delete obj.__proto__.z; //或者delete proObj.z;
13console.log(obj.z); //此时彻底没有z了
14
15
上述的代码可以变换成以下图示:
基于构造函数实现的原型继承
通过构造函数来创建对象
-
当一个函数与new结合,该函数将作为构造函数来使用,用来创建JS对象
-
JS(ES5)中没有其他语言(C++、Java)中的类,JS中通过构造函数来实现类的功能
-
在JS中构造函数也是对象,有一个重要的属性(原型 prototype),该属性与继承相关
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1/*基于构造函数实现的原型继承*/
2function Person(age, name) {
3 this.name = name;
4 this.age = age;
5}
6Person.prototype.sayHi = function() {
7 console.log("Hi,i'm " + this.name); // 此时的this指向的还是当前的对象即p1对象
8};
9var p1 = new Person(20, "Jack");
10console.log(p1.name);// Jack
11console.log(p1.age);// 20
12p1.sayHi();// Hi,i'm Jack
13
14
上述的代码可以转换为以下图示:
-
构造函数有一个重要属性(原型prototype),该属性就是实例化出来的对象的原型
-
构造函数的这个属性(原型 prototype)是真实对象,实例化的对象通过它实现属性继承
-
可通过实例化出来的对象的__proto__属性来确认原型
-
实例化的这个对象,有一个属性__proto__指向原型
-
通过判断得知实例化出来的对象的__proto__就是构造函数的prototype属性
基于构造函数实现的原型继承以及原型链的图解
注意上述的constructor
constructor是定义在原型上的,可以通过p1的constructor属性来访问p1对象的构造器,但是实质上是p1的原型中的constructor属性。
1
2
3
4
5
6
7
8
9 1console.log(p1.__proto__ === Person.prototype); // true
2
3Person.prototype.constructor
4ƒ Person(age, name) {
5 this.name = name;
6 this.age = age;
7}
8
9
基于构造函数实现的原型继承-属性操作
JS对象-对象原型继承
JavaScript的原型继承是对象-对象的继承
-
每个对象都有一个原型对象(可动态的指定原型,来改变继承关系,最原始的原型是null)
-
思考并回答三种方式创建的对象的原型都是什么?
-
多个对象继承于一个原型时,存在原型共享(节省内存如共享方法,但也带来了共享问题)
1
2
3
4
5
6
7
8
9
10
11 1//通过Object.create静态方法创建的对象的原型共享问题
2var superObj = {
3 x: 1,
4 y: 2
5};
6var subObj_First = Object.create(superObj);
7var subObj_Second = Object.create(superObj);
8subObj_First.__proto__.x = 5; //若此行写为subObj_First.x = 5;结果又是如何?
9console.log(subObj_Second.x);
10
11
上述代码中,通过Object静态方法创建了两个空对象,其中两个空对象的原型都是superObj,两个对象共享变量x和y,通过subObj_First.proto.x = 5;改变原型的x的属性的值,因此在subObj_Second.x访问x时,自身没有这个属性,会找到原型,输出5。
构造函数实现的对象-对象的原型继承的原型共享问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1function Person(name) {
2 this.name = name;
3}
4Person.prototype.age = 22;
5Person.prototype.showName = function() { console.log(this.name); };
6
7function Student(id) {
8 this.id = id;
9}
10//var p1 = new Person("Mike");Student.prototype = p1;
11Student.prototype = new Person("Mike");
12var s1 = new Student(2017001);
13var s2 = new Student(2017002);
14
15
上述的代码可以转化为下列图示:
上述代码的原型共享问题很严重,有弊端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1//测试如下代码,思考为什么,这样的继承有什么弊端
2console.log(s1.name, s1.age, s1.id);
3console.log(s2.name, s2.age, s2.id);
4s1.__proto__.name = "Jack";
5console.log(s2.name);
6s2.__proto__.__proto__.age = 99;
7console.log(s2.age);
8/*
9输出结果:
10Mike 22 2017001
11Mike 22 2017002
12Jack
1399
14*/
15
16
两个对象的name都是从person里面的name得到的,显然对于一个学生来说应该只有一个name,并且age都是唯一的,但是该代码没有实现。如果给每一个对象都添加一个name和age,会造成内存的浪费,怎么解决的呢?
通过构造函数模拟类-类的继承
模拟类-类继承的形式一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1//JS实现继承的形式 一
2function Person(name, age) {
3 this.name = name;
4 this.age = age;
5};
6Person.prototype.showName = function() { console.log(this.name); };
7
8function Student(name, age, id) {
9 Person.call(this, name, age);
10 this.id = id;
11}
12Student.prototype.__proto__ = Person.prototype;
13var s1 = new Student("xxx", 22, 2017001);
14var s2 = new Student("www", 23, 2017002);
15
16
上述的代码可以转化为如下图所示的内容:
思考:name属性添加到哪个对象上了?Person.prototype、Student.prototype还是实例化的对象上? Name添加到了实例化的对象上
推荐:将方法添加到对象的原型上(即构造函数的prototype上)便于共享,节省内存
模拟类-类继承的形式二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1//JS实现继承的形式 二
2function Person(name, age) {
3 this.name = name;
4 this.age = age;
5};
6Person.prototype.showName = function() {
7 console.log(this.name);
8};
9
10function Student(name, age, id) {
11 Person.call(this, name, age);
12 this.id = id;
13}
14Student.prototype = Object.create(Person.prototype); // 形成继承关系
15// console.log(Person.prototype.constructor); //
16// console.log(Student.prototype.constructor); //
17Student.prototype.constructor = Student; // 把指飞的constructor指向正确的内容
18var s1 = new Student("xxx", 22, 2017001);
19var s2 = new Student("www", 23, 2017002);
20
21
思考:如果不把Student.prototype.constructor指回Student,那它将指向谁?
如果不指定那么Student.prototype.constructor就会指飞。
上述的代码可以转换为一下图示
JS继承补充部分
静态方法与原型方法的区别
-
静态方法是构造器函数对象(类)的方法,原型方法是实例化对象(对象)的原型的方法
-
使用形式有什么不同,区别在哪里?(属性共享)
-
思考Object.getPrototypeOf(…)与Object.prototype.isPrototypeOf(…)
1
2
3
4
5
6
7
8
9
10
11
12
13 1//静态方法实例与原型方法实例
2var BaseClass = function() {};
3BaseClass.prototype.f2 = function() {
4 console.log("This is a prototype method ");
5};
6BaseClass.f1 = function() { //定义静态方法
7 console.log("This is a static method ");
8};
9BaseClass.f1(); //This is a static method
10var instance1 = new BaseClass();
11instance1.f2(); //This is a prototype method
12
13
再谈对象原型的constructor属性
- 因为对象实例从原型中继承了constructor,所以可以通过constructor得到实例的构造函数
(1)确定对象的构造函数名
1
2
3
4
5 1function Foo() {}
2var f = new Foo();
3console.log(f.constructor.name);
4
5
(2)创建相似对象
1
2
3
4
5
6
7
8
9 1function Constr(name) {
2 this.name = name;
3}
4var x = new Constr("Jack");
5var y = new x.constructor("Mike");
6console.log(y);
7console.log(y instanceof Constr);
8
9
(3)constructor可用于指定构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1function Person(area) {
2 this.type = 'person';
3 this.area = area;
4}
5Person.prototype.sayArea = function() {
6 console.log(this.area);
7};
8var Father = function(age) {
9 this.age = age;
10};
11Father.prototype = new Person('Beijin');
12console.log(Person.prototype.constructor); //function person()
13console.log(Father.prototype.constructor); //function person()
14Father.prototype.constructor = Father; //修正constructor指向
15console.log(Father.prototype.constructor); //function father()
16var one = new Father(25);
17
18
对象的公有属性、私有属性(回顾闭包)
涉及到访问私有属性时,需将间接访问私有变量的函数定义在构造函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1//公有属性、私有属性、特权方法
2function A(id) {
3 this.publicId = id;
4 var privateId = 456;
5 this.getId = function() {
6 console.log(this.publicId, privateId);
7 };
8}
9var a = new A(123);
10console.log(a.publicId);
11// console.log(a.privateId);
12a.getId();
13
14
在调用getId方法的时候,会访问到privateId,因此形成了闭包