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指向父级创建的实例,继承父类构造函数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构造函数

function _new(F){
    var o = {}
    F.call(o) 
    if(o !== null && typeof o === 'object') { // 如果 F 中有 return,则 o 可能不为对象
        o.__proto__ = F.prototype
    }
    return o;
}

原型继承和原型链

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(obj1.prototype);//undefined
//所有索引对象(Array Object Function)都有__proto__,只有function对象才有prototype

console.dir(obj1.__proto__);//obj对象{name:'name',printName:function(){console.log(this.name)}}
//Object.create()方法创建的对象其__proto__原型指向创建的目标对象

// ES6 中推荐使用 Object.getPrototypeOf(obj1)来获取对象原型,使用 Object.create(obj)来撞见具有指定原型的对象,使用Object.setPrototypeOf(obj1,obj) 来设置对象的原型

使用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
//所有对象的__proto__原型最终都会指向Object对象,Object对象原型为null

由于Object.create()创建的对象其__proto__直接其指向父级,所以父对象修改会影响子对象,而子对象修改无法影响父对象。

注意 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

JS中所有索引对象(Array Object Function)都有__proto__属性,但是只有function对象才有prototype属性。

JS中不论通过 var method = function(){},var method = new Function(),function method(){} 三种方法中哪一种创建的函数,都会带有prototype属性,该属性默认包含__proto__属性和constructor属性,函数prototype.constructor默认指向函数自身。

JS中索引对象的__proto__属性无法直接访问,需要通过Object.getPrototypeOf()访问(即Object.getPrototypeOf( obj ) === obj.__proto__)。通过new创建的对象,其__proto__指向其构造函数的prototype(即obj.constructor.prototype === obj.__proto__)。通过Object.create()创建的对象,其__proto__指向其父级对象(即Object.create(obj).__proto__ === obj)。

如果一个对象是Function类型的,那么本身包含的prototype和__proto__是完全不同的两个概念。__proto__对应其constructor.prototype,constructor为Function构造函数;prototype则默认是一个只有constructor属性的对象,prototype.constructor对应其自身。

var func = function(){}
console.log(func.prototype) // {constructor : func 构造函数} 对象
console.log(func.prototype.constructor == func) //函数自身 
console.log(func.__proto__ == func.constructor.prototype) // 一个匿名函数function(){}

console.log(Fucntion == func.constructor) // Fucntion构造函数
console.log(Fucntion.__proto__== Fucntion.prototype ) // 一个匿名函数function(){}
console.log(Fucntion.__proto__.__proto__) // Object对象
console.log(Fucntion.__proto__.__proto__.__proto__) // null

var newObj = new func()
console.dir(newObj.__proto__) // {constructor : func 构造函数} 对象
console.dir(newObj.__proto__.__proto__) // Object对象
console.dir(newObj.__proto__.__proto__.__proto__) // null

var obj = {}
console.dir(obj.__proto__) // Object对象
console.dir(obj.__proto__.__proto__) // null

var arr = [] 
console.dir(arr.__proto__) // Array对象
console.dir(arr.__proto__.__proto__) // Object对象
console.dir(arr.__proto__.__proto__.__proto__) // null

图片转载自http://www.mollypages.org/misc/js.mp

JavaScript ååé¾

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值