JS: 构造函数 继承 原型链
创建构造函数
- //1. 定义一个构造函数
- function Obj(a) {
- this.a = a || 'a';
- }
- console.dir(Obj.prototype.constructor);// Obj(函数自身)
- /*------- 构造函数的 prototype.constructor 应指向函数自身 -------*/
- //2. 在该构造函数的原型中 定义该构造函数创建的所有对象都会继承的公共方法
- Obj.prototype.getA = function() {
- return this.a;
- }
- var o1 = new Obj('1');
- console.log(o1.getA()) // '1'
继承内部属性和方法
- function obj(a, b) {
- // 通过改变父类构造函数的执行上下文, 继承父类构造函数中定义的属性
- Obj.call(this, a);
- this.b = b;
- }
- var o2 = new obj('2', ['a','b']);
- console.log(o2.getA()); // 报错
由于 getA 方法被定义在 Obj.prototype 中, call 方法只能使 obj 调用 Obj 内部定义的方法, 故 Obj.prototype 中定义的方法没有被 obj 继承. 要解决此问题可以将 getA 定义在 Obj 对象内部, 但是这样会在每一个实例中创建一个 getA 方法, 对于公用函数来说这样做显然会造成资源浪费, 将公共方法定义在构造函数的原型中来继承则更加适合.
构造函数结合原型继承
继承 prototype 中属性和方法
- // 通过将 prototype 指向父级创建的实例, 继承父类构造函数 prototype 中定义的方法
- obj.prototype = new Obj(); // 此时 obj.prototype.constructor 为 Obj
- obj.prototype.constructor = obj; // 修正 constructor 指向为 obj 函数自身, 避免修改原型链时造成的 constructor 丢失
- var o3 = new obj('3', ['a','b']);
- console.log(o2.getA()); //'3'
注意 obj.prototype = new Obj()与 obj.prototype = Obj.prototype 的区别:
前者将 obj 的 prototype 指向了 Obj 创建的一个实例, 该实例的__proto__指向 Obj.prototype(通过 new 创建的对象, 其__proto__总是指向其构造函数的 prototype)
后者将 obj 和 Obj 的 prototype 指向同一个原型对象, 如果定义子构造函数的 prototype 中的属性或方法将影响到父级.
构造函数在实例化对象时, 每一个子类都将调用一次父类构造函数.
上面代码中
obj.prototype = new Obj();
可写作
- obj.prototype = (function() {
- function O() {
- }
- O.prototype = Obj.prototype;
- return new O();
- })();
封装成继承函数
- // 继承函数
- function extend(son, father) {
- function F() {
- }
- F.prototype = father.prototype;
- son.prototype = new F();
- son.prototype.constructor = son;
- }
这样在创建 obj 实例时, 减少了对父类的构造函数的调用次数, 在继承层级较多时, 可以减少内存占用.
使用这种继承方式时要尽量减少继承层级, 参考下面例子, 创建一个 E 对象实例共调用 5 次父级构造函数.
- //A 类
- function A() {
- console.log('A()');
- }
- A.prototype.print = function() {
- console.log('msg');
- }
- //B 类
- function B() {
- A.call(this);
- console.log('B()');
- }
- extend(B, A);
- //C 类
- function C() {
- B.call(this);
- console.log('C()');
- }
- extend(C, B);
- //D 类
- function D() {
- C.call(this);
- console.log('D()');
- }
- extend(D, C);
- //E 类
- function E() {
- D.call(this);
- console.log('E()');
- }
- extend(E, D);
- // 创建一个 E 的实例
- var e = new E(); //A() B() C() D() E()
- e.print(); //msg.
构造函数继承对象
在前面的继承中, 是将子构造函数的 prototype 指向父构造函数的实例, 下面的代码会将一个构造函数的 prototype 指向一个对象, 并用该构造函数创建实例.
- // 继承函数
- function clone(object) {
- function F() {
- }
- F.prototype = object;// 指定一个对象
- return new F();
- }
- // 父级
- var Obj1 = {
- a: 'a',
- getA: function() {
- return this.a;
- }
- }
- // 子级
- var Obj2 = clone(Obj1);
- Obj2.b = ['b'];
- Obj2.getB = function() {
- return this.b.join(',');
- }
- // 创建实例 1
- var obj1 = clone(Obj2);
- obj1.b.push('b1');
- // 创建实例 2
- var obj2 = clone(Obj2);
- obj2.b.push('b2');
- console.log(obj1);//b,b1,b2
- console.log(obj2);//b,b1,b2
- console.log(Obj2);//b,b1,b2
使用这种继承方式时, 创建的所有实例虽然会返回一个新对象, 但其引用和父级指向同一个内存地址, 只有重新赋值时才会分配新的内存地址, 如果直接操作其修改会影响到父级.
- // 创建实例 1
- var obj1 = clone(Obj2);
- obj1.b = [];
- obj1.b.push('b1');
- // 创建实例 2
- var obj2 = clone(Obj2);
- obj2.b = [];
- obj2.b.push('b2');
- console.log(Obj2);//b
- console.log(obj1);//b1
- console.log(obj2);//b2
原型继承和原型链
new 关键字只能对 (构造) 函数使用, 如果要继承的目标是一个对象需要通过设置 prototype 来实现, 而且在存在操作实例时影响父级的风险. 使用 Object.create()方法可以以一个对象为目标来创建一个新对象, 并实现继承.
- var obj = {
- name:'name',
- printName:function(){
- console.log(this.name)
- }
- }
- //var obj2 = new obj1();// 报错,
- //new 关键字只能用 function(构造函数)来 new 对象, 不能直接用对象来 new 对象
- var obj1 = Object.create(obj);
- obj1.name1 = 'name1';
- console.dir(obj1);//{
- name1:'name1'
- },obj1 内部没有 name 属性, 只有 name1 属性
- obj1.printName();//name,obj 继承并能够调用 printName 方法和读取 name 属性
- console.dir(obj2.prototype);//undefined
- // 所有索引对象 (Array Object Function) 都有__proto__, 只有 function 对象才有 prototype
- console.dir(obj2.__proto__);//obj 对象{
- name:'name',printName:function(){
- console.log(this.name)
- }
- }
- //Object.create()方法创建的对象其原型指向创建的目标对象
使用 Object.create()方法可以实现多重继承.
- var obj = {
- name:'name',
- print:function(key){
- console.log(this[key])
- }
- }
- var obj1 = Object.create(obj);
- obj1.name1 = 'name1';
- var obj2 = Object.create(obj1);
- obj2.name2 = 'name2';
- obj2.print('name');//name
- obj2.print('name1');//name1
- obj2.print('name2');//name2
- console.dir(obj2);//{
- name2:'name2'
- }
- //obj2 内部没有 name 属性, 只有 name2 属性, 但继承了 obj1 和 obj 的属性和方法
- console.dir(obj2.__proto__);//obj1 对象
- console.dir(obj2.__proto__.__proto__);//obj 对象
- console.dir(obj2.__proto__.__proto__.__proto__);//Object 对象
- console.dir(obj2.__proto__.__proto__.__proto__.__proto__);//null
- // 所有对象的原型最终都会指向 Object 对象, Object 对象原型为 null
由于 Object.create()创建的对象原型直接指向父级, 所以父对象修改会影响子对象, 而子对象修改无法影响父对象.
注意 Object.create 方法不管参数是对象还是构造函数, 都只能创建出对象.
如果创建的目标是函数, 则创建结果是__proto__指向目标函数的空对象({ } , 不会报错)
在前面已经多次涉及原型链知识, 再提一下
- var func0 = function(){
- this.name0 = 'func0';
- }
- func0.prototype.print0 = function(){
- console.log(this.name0);
- }
- console.dir(func0.prototype); //{
- print0:function(){
- console.log(this.name0)
- }
- }原型对象
- console.dir(func0.prototype.constructor);//func0
- // 函数的 prototype.constructor 默认指向该函数自身
- var func1 = function (){
- this.name1 = 'func1'
- func0.call(this);
- }
- func1.prototype = new func0();
- func1.prototype.print1 = function(){
- console.log(this.name1);
- }
- console.dir(func1.prototype); //func0 实例对象
- console.dir(func1.prototype.constructor);//func0 由于将原型指向了 func0 实例, 导致构造器指向 func0
- func1.prototype.constructor = func1;
- // 手动将构造器指向修正回函数自身, 可以确保由 fun1 创建的实例 instanceof 为 fun1, 避免造成 instanceof 失真
- console.dir(func1.prototype.__proto__);//{
- print0:function(){
- console.log(this.name0)
- }
- }原型对象
- // 由于 func1.prototype 为 func0 实例, 故 func1.prototype.__proto__指向 func0.prototype
- // 通过 new 创建的对象, 其__proto__总是指向其构造函数的 prototype
- console.dir(func1.__proto__);//Function 对象
- //func1 是通过 new Function()创建实例, Function 构造函数对象的 prototype 是 Function 对象
- console.dir(func1.__proto__.__proto__);//Object 对象
- // 所有引用类型数据原型都是原自 Object 对象
- //Function 对象的构造函数的 prototype 是 Object 对象
- console.dir(func1.__proto__.__proto__.__proto__);//null
- // 已经查找到原型链终点, 即 Object 对象的__proto__为 null
来源: https://www.2cto.com/kf/201810/782656.html