专注于WEB前端开发,HTML基础知识,CSS基础知识,HTML5和CSS3,Javascript,Jquery,Jquery插件,移动开发,微信开发等技术话题

当前位置:CSSTOP主页 > 前端积累 > 正文

JS中原型继承的工作原理

当查找一个对象的属性时,JS会向上遍历原型链,直到找到给定的属性名称为止,如果没找到就是undefined。
大多数的JS的实现用_proto_属性来表示一个对象的原型链,以下代码展示了JS引擎如何查找属性。
function getProperty(obj, prop) {
    if (obj.hasOwnProperty(prop))
        return obj[prop]
 
    else if (obj.__proto__ !== null)
        return getProperty(obj.__proto__, prop)
 
    else
        return undefined}
让我们举一个比较简单的例子,假设有个三维点,坐标x、y、z,同时有print打印方法。
现在我们创建一个对象point,具有x、y、z和print属性,为了能创建一个新的三维坐标点,我们需要创建一个新的对象,使得它的_proto_指向point,继承point。类似C++中的OOP,例如point为基类,创建新的point对象为原先point的子类。
var Point = {
    x: 0,
    y: 0,
    z: 0,
    print: function () { console.log(this.x, this.y, this.z); }};
    var p = {x: 10, y: 20, z: 30,__proto__: Point};p.print(); // 10 20 30
但是js工程师一般都不会这样写原型继承,他们如下写出:
function Point(x, y) {
    this.x = x;
    this.y = y;
    this.z = z;}
    Point.prototype = {
    print: function () { console.log(this.x, this.y, this.z); }};
    var p = new Point(10, 20, 30);
    p.print(); // 10 20 30
这里涉及到了new运算符的工作原理
new后面跟的不是类,而是构造函数 ,用new构造函数生成实例对象,有一个很明显的缺点,就是每个实例无法共享同一个属性和方法。在C++中,如果类中定义了一个static成员,那么所有该类的实例都共享该成员。而JS中每一个实例的对象都有自己属性的副本,这样比较浪费空间,而且无法实现数据的共享。
基于以上new构造函数的缺陷,JS创始人为构造函数添加了prototype属性
prototype属性的引入
JS规定每一个构造函数都有prototype属性,该属性指向另一个对象,另一个对象中所有的属性和方法都会被构造函数的实例引用。
这个属性包含一个对象,所有需要共享的属性和方法放入这个对象中,而不需要共享的属性和方法放入构造函数中。实例对象一旦创建成功,就会自动引用prototype对象中的方法和属性,即实例对象的属性和方法分为两种,一种是本地的,即放入构造函数中的属性和方法,一种是引用的,即放入prototype对象。例如以下代码:
function DOG(name){
    this.name = name;
  }
  DOG.prototype = { species : '犬科' };
 
  var dogA = new DOG('大毛');
  var dogB = new DOG('二毛');
 
  alert(dogA.species); // 犬科
  alert(dogB.species); // 犬科
在这个例子中,species属性放入prototype对象中,那么实例dogA和dogB共用species属性。只要其中一个实例的species发生改变,则会影响所有实例的species

Prototype模式的验证方法
isPrototypeOf

这个方法用来判断prototype对象和某个实例之间的关系,例如
alert(DOG.prototype.isPrototypeOf(dogA)); //true
hasOwnProperty 每一个实例对象都有一个该方法,用来判断该实例中的某个属性是来自本地属性,还是继承自原型对象属性。例如
alert(dogA.hasOwnProperty(name))//true
in运算符

in运算符可以判断某个属性是否属于实例对象,不管是本地属性还是继承自原型对象属性。如
alert("name" in dogA); //true
alert("species" in dogA); //true
in运算符还可以遍历某个对象的所有属性,如
for (var prop in dogA) {
alert(prop);
}
测试
有一道阿里的在线前端笔试题,题目如下:
现在有如下的代码:
var foo = 1;
function main(){
    console.log(foo);
    var foo = 2;
    console.log(this.foo)
    this.foo = 3;}
1.请给出以下两种方式调用函数时,输出的结果,并说明原因
var m1 = main();
var m2 = new main();
2.如果需要var m1= main()产生的结果与前面m2产生结果一样,应该如何改造main函数
第一题解答:首先根据JS的变量提升规则,可以知道,全局的foo被main函数屏蔽了,main函数在内部定义了一个foo同名的变量,该变量在第一个console之前只定义而未赋值,故为undefined(undefined有两种情况会出现,已定义未赋值,未定义)。而在第二个console的时候,this指向的是window,故输出为1