当查找一个对象的属性时,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属性
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对象和某个实例之间的关系,例如
alert(DOG.prototype.isPrototypeOf(dogA)); //true
每一个实例对象都有一个该方法,用来判断该实例中的某个属性是来自本地属性,还是继承自原型对象属性。例如
alert(dogA.hasOwnProperty(name))//true
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