js原型及原型链
Posted fermin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js原型及原型链相关的知识,希望对你有一定的参考价值。
js对象与其它面向对象编程语言的差异
-- JAVA与C++等语言中的对象,是先构造一个类抽象事物,再通过类实例化一个个对象。但javascript中,中不区分类和实例的概念,而是通过原型(prototype)来实现面向对象的封装,继承和多态,从而实现面向对象编程。
什么是面向对象编程?
- 将现实世界中各种复杂的关系,抽象为一个个对象,由对象的之间的分工和合作,完成对真实世界的模拟
- 具有灵活性,代码可重用性,模块性等特点,容易维护和开发,适合多人合作的大型软件项目
什么是js原型?
- 在JavaScript中,原型也是一个对象,原型所有的属性和方法都会被构造函数的实例继承
- JavaScript的对象中都包含了一个__proto__内部属性,这个属性指向的就是该对象的原型
什么是构造函数?
- 专门用来生成对象的函数,内部使用了this对象,对构造函数使用
new
运算符,就能生成实例,并且this
变量会绑定在实例对象上 - 它描述了一些对象的基本结构,是一个模板 ,使生成的对象有相同的结构
- 以区别普通函数,构造函数名字首字母大写
- 构造函数中,若return的是原始数据,new命令会忽略该语句返回this对象;若return的是一个和this无关的新对象,new返回的会是该新对象。
什么是原型链?
-- 构造函数有prototype属性,会指向一个原型对象,这样通过构造函数生成的实例就会继承来自原型对象的属性和方法,而原型对象又是它所属构造函数形成的实例,会继承来自它的原型对象的属性和方法,这样层层继承,直到原型顶部为null为止,这样原型层层连接起来,就构成了原型链。 如下图所示
- 所有的对象,都有__proto__属性,这个属性指向该对象的原型
- 所有函数对象,除了__proto__属性之外,还有prototype属性,该属性用来设置实例对象的__proto__属性(函数对象作为构造函数创建实例时,函数的prototype属性会赋值给实例属性__proto__指向原型) (构造函数.prototype === 实例对象.__proto__)
- 所有原型对象都有"constructor"属性,该属性指向 创建了基于该原型的实例对象的 构造函数
什么是封装
--就是将属性和方法封装成一个对象,隐藏属性和方法的实现细节,仅对外公开接口。
js如何封装
简单的封装--原始模式生成实例对象
缺点:
- 生成实例代码重复过多
- 实例与原型之间关联不大
eg:
<script> // 创建一个cat原型对象 // 把两个属性封装在一个对象里面 var Cat = { name: ‘‘, color: ‘‘ }; // 生成两个实例对象 var cat1 = {}; // 创建一个空对象 cat1.name = "大毛"; // 按照原型对象的属性赋值 cat1.color = "黄色"; var cat2 = {}; cat2.name = "二毛"; cat2.color = "黑色"; </script>
原始模式--用函数生成实例对象
优点:
- 改进了原始模式的一些代码重复的问题
缺点:
- 实例与原型之间关联仍不大
eg:
<script> function Cat(name, color) { return { name: name, color: color }; } var cat1 = Cat("大毛","黄色"); //调用函数生成实例对象 var cat2 = Cat("二毛","黑色"); </script>
构造函数模式
优点:
- 解决了原始模式代码重复的问题
- 生成的实例对象与原型有着紧密的联系
缺点:
- 共有不变的属性和方法,在每个实例中都会再次生成,会多占用一些内存。
<script> function Cat(name, color) { this.name = name; this.color = color; } var cat1 = new Cat("大毛", "黄色"); var cat2 = new Cat("二毛", "黑色"); alert(cat1.name); // 大毛 alert(cat1.color); // 黄色 alert(cat1.constructor === Cat); //true alert(cat2.constructor === Cat); //true alert(cat1.hasOwnProperty("constructor")); //false; alert(cat1.__proto__.hasOwnProperty("constructor")); //true //constructor属性在原型中指向构造函数 </script>
Prototype模式
优点:
- 解决了原始模式代码重复的问题
- 生成的实例对象与原型有着紧密的联系
- 不变的属性和方法,在原型中被共享,减少了内存的占用,提高了运行效率
将不变的属性和方法,直接定义在prototype
对象上
<script> function Cat(name, color) { this.name = name; this.color = color; } Cat.prototype.type = "动物"; Cat.prototype.eat = function () { alert("吃鱼"); }; var cat1 = new Cat("大毛", "黄色"); var cat2 = new Cat("二毛", "黑色"); alert(cat1.type); // 动物 cat1.eat(); // 吃鱼 alert(Cat.prototype.isPrototypeOf(cat1)); //true alert(Cat.prototype.isPrototypeOf(cat2)); //true alert(cat1.hasOwnProperty("name")); // true alert(cat1.hasOwnProperty("type")); // false alert("name" in cat1); // true alert("type" in cat1); // true </script>
继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。
使用构造函数继承:
一 构造函数绑定
--用call或apply方法,将父对象的构造函数绑定在子对象,实现对父对象属性的继承
<script> function Animal() { this.type = "动物"; } function Cat(name, color) { this.name = name; this.color = color; } function Cat(name, color) { Animal.apply(this); this.name = name; this.color = color; } var cat1 = new Cat("大毛", "黄色"); alert(cat1.type); // 动物 alert(cat1.hasOwnProperty(‘type‘)); //true type已是实例中自身的属性 </script>
二、 prototype模式
<script> function Animal() { this.type = "动物"; } function Cat(name, color) { this.name = name; this.color = color; } Cat.prototype = new Animal(); // 替换prototype对象(原先值被删除,被赋予一个新值) // 此时Cat.prototype.constructor也变为Animal Cat.prototype.constructor = Cat; // 将constructor属性指回原来的构造函数 // 防止继承链的紊乱 var cat1 = new Cat("大毛", "黄色"); alert(cat1.type); // 动物 alert(cat1.hasOwnProperty(‘type‘)); //false //type继承Animal一个实例属性, //Animal的一个实例为cat1原型 alert(Animal.isPrototypeOf(cat1)); //false alert(Cat.prototype.isPrototypeOf(cat1)); //true </script>
三 直接继承prototype
优点:效率比较高(不用执行和建立Animal的实例了),节省内存
缺点:Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
<script> function Animal() {} Animal.prototype.type = "动物"; function Cat(name, color) { this.name = name; this.color = color; } Cat.prototype = Animal.prototype; //不用new Animal(); Cat.prototype.constructor = Cat; //隐士改变Animal的构造函数变Cat var cat1 = new Cat("大毛", "黄色"); alert(cat1.type); // 动物 alert(cat1.hasOwnProperty(‘type‘)); //false alert(Animal.prototype.isPrototypeOf(cat1)); //true alert(Animal.prototype.constructor); // Cat(){} //确认Animal的构造函数变为Cat </script>
四、 利用空对象作为中介
<script> function Animal() {} Animal.prototype.type = "动物"; function Cat(name, color) { this.name = name; this.color = color; } function extend(Child, Parent) { // YUI库如何实现继承的方法 var F = function () {}; // 创建一个空函数对象 F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; // 备用属性指向父对象的prototype属性 // 实现继承的完备性 } extend(Cat, Animal); var cat1 = new Cat("大毛", "黄色"); alert(cat1.type); // 动物 alert(cat1.hasOwnProperty(‘type‘)); // false alert(Animal.prototype.isPrototypeOf(cat1)); // true alert(Animal.prototype.constructor); // Animal(){} </script>
五、 拷贝继承
-- 把父对象的所有属性和方法,拷贝进子对象,去实现继承
<script> function Animal() {} Animal.prototype.type = "动物"; function Cat(name, color) { this.name = name; this.color = color; }
//父对象的prototype属性一一拷贝给子对象的prototype属性中 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 cat1 = new Cat("大毛", "黄色"); alert(cat1.type); // 动物 alert(cat1.hasOwnProperty(‘type‘)); //false alert(Animal.prototype.isPrototypeOf(cat1)); //false alert(Cat.prototype.isPrototypeOf(cat1)); //true alert(Animal.prototype.constructor); // Animal(){}
</script>
非构造函数的继承
将两个非构造函数的普通对象形成继承关
object()方法:
<script> //将子对象的prototype属性,指向父对象 //让子对象与父对象连在一起。 function object(o) { function F() {} F.prototype = o; return new F(); } var Chinese = { nation: ‘中国‘ }; var Doctor = object(Chinese); Doctor.career = ‘医生‘; alert(Doctor.nation); //中国 </script>
<script> function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { SuperType.call(this); //继承了SuperType } var instance1 = new SubType(); instance1.colors.push("black"); //实例1中,属性color加一属性值black alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green" </script>
构造函数模式的一些问题:
- 共有不变的属性和方法,在每个实例中都会再次生成,会多占用一些内存
- 可以将共有的属性和方法,在内存中只生成一次,然后所有实例都指向那个内存地址
浅拷贝:只拷贝对象中基本类型的数据
<script> function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; //复制成另一对象的一副本,且对象中的属 //性若是地址也会相同的复制过来,造成一些属性对象的享
//可能会影响到父对象属性, } c.uber = p; return c; } var Chinese = { nation: ‘中国‘ }; var Doctor = extendCopy(Chinese); Doctor.career = ‘医生‘; alert(Doctor.nation); // 中国 </script>
深拷贝:
<script> function deepCopy(c, p) { 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 Chinese = { nation: ‘中国‘ }; var Doctor = deepCopy(Chinese) </script>
原型链继承的一些问题:
- 当原型变成另一个类型的实例时,该原型会拥有上一类型的属性,会共享下去
- 创建子类型实例时,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
eg:
<script> function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() {} //继承了SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black" //这样SubType.prototype会有一个colors属性,这样所有的SubType会共享这一实例,造成一些错误,你不知道还好,一直到
</script>
六、 Prototype模式的验证方法
- isPrototypeOf() 判断某个
proptotype
对象和某个实例之间的关系。 //SubType.prototype.isPrototypeOf(incetence1); //true - hasOwnProperty() 判断某一个属性到底是本地属性,还是继承自
prototype
对象的属性。// incetence1.hasOwnProperty("color"); // true in
运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。"color" in inctence1 //true
以上是关于js原型及原型链的主要内容,如果未能解决你的问题,请参考以下文章
对js原型链及继承的理解:__proto__&prototpye
关于JS面向对象中原型和原型链以及他们之间的关系及this的详解