JavaScript 关于继承和原型链

JavaScript是一种基于对象(object-based)的语言,但是不是一种真正的面向对象编程(OOP)的语言。而JavaScript中的继承是通过原型链的模式实现的。

1. js中创建对象-用构造器
function Animal(name){
	this.name = name;
}
var dog = new Animal("贝贝");
alert(dog.constructor == Animal); //true

在JavaScript中,new命令后面跟的不识类,而是构造函数,使用后会生成一个对象的实例。生成的实例动向会自动含有一个constructor属性,指向它们的构造函数

  • new运算符的缺点:
    • 无法共享属性和方法
    • 每一个实例对象,都有自己的属性和方法的剧本。无法做到数据共享,也是极大的资源浪费
  • prototype属性
    • 构造函数中引入了prototype属性,实例对象中没有该属性,prototype属性包含一个对象需要共享的属性和方法,不需要共享的属性和方法,就放在构造函数里面。
    • 对象一旦创建,将会自动引用prototype对象的属性和方法。
    • prototype属性中的属性和方法,在所有实例对象中都是一致的,都是同一个内存地址,指向prototype对象,因此可以提高运行效率。
  • 由于所有的实例对象共享同一个prototype对象,prototype对象就好像是实例对象的原型,实例好像继承了prototype对象。
  • prototype模式的验证方法
    • isPrototypeOf()
      • 用来判断:某个prototype对象和某个实例之间的关系。
      alert(Animal.prototype.isPrototypeOf(dog)); //true
      
    • hasOwnProperty()
      • 实例对象的方法,用来判断某一个属性到底是本地属性,还是继承而来的属性
    • in运算符
      • 可以用来判断,某个实例是否含有某个属性,不管是不是本地属性
      • 还可以用来遍历某个对象的所有属性
      for(var prop in dot){
      	console.log('dog[" + prot + "]=" + dog[prop]);
      }
      
2. 构造函数的继承

例子:

function Animal() {
	this.species = "动物";
}
function Cat(name, color) {
	this.name = name
	this.color = color
}
  • ① 构造函数绑定
    • 使用call或apply方法,将父对象的构造函数绑定在子对象上,即
    function Cat(name, color) {
    	Animal.apply(this, arguments);
    	this.name = name;
    	this.color = color;
    }
    var cat = new Cat("贝贝", "white");
    console.log(cat.species);//动物
    
  • ② prototype模式
    • 将Cat的prototype指向一个Animal的实例
    Cat.prototype = new Animal();
    // 完全替换了prototype的值
    Cat.prototype.constructor = Cat;
    // prototype有一个constructor对象,指向它的构造函数
    // 如果不修改替换后的prototype中constructor的值
    // 则其指向的构造函数则为Animal
    var cat = new Cat("贝贝", "white");
    console.log(cat.species); // 动物
    
    • 关于constructor属性的作用是什么

    constructor属性不影响任何JavaScript的内部属性。constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。https://www.zhihu.com/question/19951896/answer/13457869

    var a,b;
    (function(){
    	function A (arg1,arg2) {
    		this.a = 1;
    		this.b=2; 
    	}	
    	A.prototype.log = function () {
    		console.log(this.a);
    	}
    	a = new A();
    	b = new A();
    })()
    a.log(); // 1
    b.log(); // 1
    //通过以上代码我们可以得到两个对象,a,b,他们同为类A的实例。因为A在闭包里,所以现在我们是不能直接访问A的,那如果我想给类A增加新方法怎么办?
    // a.constructor.prototype 在chrome,firefox中可以通过 a.__proto__ 直接访问
    a.constructor.prototype.log2 = function () {
    	console.log(this.b)
    }
    a.log2(); // 2
    b.log2();	// 2
    //或者我想知道a的构造函数有几个参数?
    a.constructor.length // 2
    //作者:小鱼二
    //链接:https://www.zhihu.com/question/19951896/answer/67551712
    //来源:知乎
    //著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
  • ③ 直接继承prototype
    • 对第二种额改进。由于Animal对象中,不变的属性写入了Animal.prototype。所以,我们可以让Cat()跳过Animal(),直接继承Animal.prototype.
    function Animal(){}
    Animal.prototype.species = '动物';
    Cat.prototype = Animal.prototype;
    Cat.prototype.constructor = Cat();
    var cat = new Cat("贝贝", "white");
    console.log(cat.species); // 动物
    
    与前一种方法相比
    • 优点:效率比较高(无需执行和建立Animal的实例),比较省空间。
    • 缺点:Cat.prototype和Animal.prototype指向了同一个对象,任何对Cat.prototype的修改都会反映到Animal.prototype
  • ④ 利用空对象作为中介
    var F = function(){};
    F.prototype = Animal.prototype;
    Cat.prototype = new F();
    Cat.prototype.constructor = Cat;
    
    F是空对象,所以几乎不占内存,此时,修改Cat.prototype则不会影响到Animal.prototype
    //将采用空对象的方法封装成一个函数
    function extend(Child, Parent) {
    	var F = function(){};
    	F.prototype = Parent.prototype;
    	Child.prototype = new F();
    	Child.prototype.constructor = Child;
    	Child.uber = Parent.prototype;
    	// 这等于在子对象上打开一条通道,可以直接调用父对象的方法。
    	//这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
    }
    //使用
    extend(Cat, Animal);
    var cat = new Cat("贝贝", "white");
    console.log(cat.species); // 动物
    
  • ⑤ 拷贝继承
    换一种思路:纯粹采用“拷贝”的方式来实现继承。简单来说,如果吧父对象的所有属性和方法,拷贝进子对象,也能够实现继承。
    function Animal(){};
    Animal.prototype.species = "动物";
    function extend2(Child, Parent) {
    	var p = Parent.prototype;
    	var c = Child.prototype;
    	for(var i in p) {
    		c[i] = p[i];
    	}
    	c.uber = p;
    }
    //使用
    extend2(Cat, Animal);
    var cat = new Cat("贝贝", "white");
    console.log(cat.species); // 动物
    
3. 非构造函数的继承

不使用构造函数实现“继承”
例子

var Chinese = { nation: "中国" };
var Doctor = { career: "医生" };

目的:如何让“Doctor”去继承“中国人”
此时Doctor和Chinese都是普通对象,而非构造函数

  • ① object()方法
    function object (o) {
    	function F(){};
    	F.prototype = o;
    	return new F();
    }
    //使用
    var Doctor = object(Chinese);
    Doctor.career = "医生";
    console.log(Doctor.nation); // 中国
    
    object的作用:就是把子对象的prototype属性,指向了父对象,从而使得子对象与父对象连在一起。
  • ② 浅拷贝
    把父对象的属性全部拷贝给子对象
    function extendCopy(p) {
    	var c = {};
    	for(var i in p){
    		c[i] = p[i];
    	}
    	c.uber = p;
    	return c;
    }
    //使用
    var Doctor = extendCopy(Chinese);
    Doctor.career = '医生';
    console.log(Doctor.nation); // 中国
    
    问题:当父对象中的某一个属性为数组或者另一个对象时,子对象通过上述方法获得的只是一个内存地址,而不是真正的拷贝,因此存在父对象被篡改的可能。
  • ③ 深拷贝
    把父对象的属性全部拷贝给子对象,当父对象的某个属性为数组或对象的时候,递归调用浅拷贝
    function deepCopy(p, c){
    	var c = c || {};
    	for(var i in p){
    		if(typeof p[i] === 'object'){
    			c[i] = (p[i].constructor === Array) ? [] : {};
    			deepCopy(p[i], c[i]);
    		} else {
    			c[i] = p[i];
    		}
    	}
    	return c;
    }
    //使用
    var Doctor = deepCopy(Chinese);
    //此时若父对象中有属性为数组,此时子对象修改该属性则不会影响到父对象
    

参考:
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值