面试问js原型怎么理解
发布时间:2025-05-22 08:25:58 发布人:远客网络
一、面试问js原型怎么理解
JavaScript对象是动态的属性“包”(指其自己的属性)。JavaScript对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。下面的代码将演示,当访问一个对象的属性时会发生的行为:
//假定有一个对象o,其自身的属性(ownproperties)有a和b:
//o的原型o.[[Prototype]]有属性b和c:
//最后,o.[[Prototype]].[[Prototype]]是null.
//这就是原型链的末尾,即null,
//根据定义,null没有[[Prototype]].
//{a:1,b:2}--->{b:3,c:4}--->null
//a是o的自身属性吗?是的,该属性的值为1
//b是o的自身属性吗?是的,该属性的值为2
//o.[[Prototype]]上还有一个'b'属性,但是它不会被访问到.这种情况称为"属性遮蔽(propertyshadowing)".
//c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
//c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4
//d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
//d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有.
//o.[[Prototype]].[[Prototype]]为null,停止搜索,
创建一个对象它自己的属性的方法就是设置这个对象的属性。唯一例外的获取和设置的行为规则就是当有一个getter或者一个setter被设置成继承的属性的时候。
JavaScript并没有其他基于类的语言所定义的“方法”。在 JavaScript里,任何函数都可以添加到对象上作为对象的属性。函数的继承与其他的属性继承没有差别,包括上面的“属性遮蔽”(这种情况相当于其他语言的方法重写)。
当继承的函数被调用时,this指向的是当前继承的对象,而不是继承的函数所在的原型对象。
//当调用o.m时,'this'指向了o.
//p是一个对象,p.[[Prototype]]是o.
//调用p.m时,'this'指向p.
//此时的'this.a'即p.a,即p的自身属性'a'
二、使用不同的方法来创建对象和生成原型链
EDIT
//o这个对象继承了Object.prototype上面的所有属性
//所以可以这样使用o.hasOwnProperty('a').
//hasOwnProperty是Object.prototype的自身属性。
//Object.prototype的原型为null。
//o--->Object.prototype--->null
//(indexOf,forEach等方法都是从它继承而来).
//a--->Array.prototype--->Object.prototype--->null
//函数都继承于Function.prototype
//(call,bind等方法都是从它继承而来):
//f--->Function.prototype--->Object.prototype--->null
在 JavaScript中,构造器其实就是一个普通的函数。当使用new操作符来作用这个函数时,它就可以被称为构造方法(构造函数)。
//g是生成的对象,他的自身属性有'vertices'和'edges'.
//在g被实例化时,g.[[Prototype]]指向了Graph.prototype.
ECMAScript 5中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用create方法时传入的第一个参数:
//a--->Object.prototype--->null
//b--->a--->Object.prototype--->null
console.log(b.a);//1(继承而来)
//c--->b--->a--->Object.prototype--->null
console.log(d.hasOwnProperty);//undefined,因为d没有继承Object.prototype
ECMAScript6引入了一套新的关键字用来实现class。使用基于类语言的开发人员会对这些结构感到熟悉,但它们是不一样的。 JavaScript仍然是基于原型的。这些新的关键字包括class,constructor,static,extends,和super.
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。遍历对象的属性时,原型链上的每个可枚举属性都会被枚举出来。
检测对象的属性是定义在自身上还是在原型链上,有必要使用hasOwnProperty方法,所有继承自Object.proptotype的对象都包含这个方法。
hasOwnProperty是 JavaScript中唯一一个只涉及对象自身属性而不会遍历原型链的方法。
注意:仅仅通过判断值是否为undefined还不足以检测一个属性是否存在,一个属性可能存在而其值恰好为undefined。
6.不好的实践:扩展原生对象的原型
一个经常被用到的错误实践是去扩展Object.prototype或者其他内置对象的原型。该技术被称为 monkey patching,它破坏了原型链的密封性。尽管,一些流行的框架(如 Prototype.js)在使用该技术,但是并没有足够好的理由要用其他非标准的方法将内置的类型系统搞乱。我们去扩展内置对象原型的唯一理由是引入新的 JavaScript引擎的某些新特性,比如Array.forEach。
二、JavaScript中什么是prototype原型对象它有和作用
在JavaScript中,prototype对象是实现面向对象的一个重要机制。每个函数就是一个对象(Function),函数对象都有一个子对象prototype对象,类是以函数的形式来定义的。prototype表示该函数的原型,也表示一个类的成员的集合。在通过new创建一个类的实例对象的时候,prototype对象的成员都成为实例化对象的成员。
1、该对象被类所引用,只有函数对象才可引用;
2、在new实例化后,其成员被实例化,实例对象方可调用。
同时,函数是一个对象,函数对象若直接声明成员,不用被实例化即可调用。
三、JavaScript原型,原型链 有什么特点
JavaScript里所有的东西都是对象.对象是属性的集合.数字,字符串,布尔值等原始值是"伪对象",它们同样拥有属性,但是是在栈上分配并按值传递.而其他的对象是堆上分配并按引用传递.
一个很重要的概念是,函数也是对象,能够作为变量的值,返回值,参数或者属性的值.函数对象特殊的地方是能通过"xxx()"语法执行包含在xxx函数对象内的代码.因为这一特殊性, typeof xxx将会返回function,但这只是一种便利设施.
1.2对象的属性可以动态添加和删除
1.3除了宿主对象,其它对象皆由构造函数创建
要有对象,就先要有创建对象的方法.
在C++/Java等语言,这个方法就是实例化XXX类的一个实例xxx.
而在JavaScript的世界里实际没有类的东西,当然仍然可以用"类"和"实例"等惯用语来描述JavaScript中类似的行为,但其机制是完全不同的. JavaScript的对象是由构造函数创建的,每个对象都有constructor属性表示创建该对象的构造函数:
functionTest(){this.a="hello";}
vartest=newTest();//由Test构造函数创建
o_.a="hello";//由Object构造函数创建
构造函数也是对象,那构造函数是由什么创建?内建的Function函数:
Test=newFunction(["a","b"],"alert(a+b);");
Function函数又是由什么创建?实际上Function是本机代码实现的固有对象.不过为了一致性, Function也有constructor属性,该属性指向它自己.接上面的代码:
alert(Test.constructor.constructor===Test.constructor);//true
alert(Test.constructor===Object.constructor);//true
prototype是构造函数的一个属性,该属性指向一个对象.而这个对象将作为该构造函数所创建的所有实例的基引用(base reference),可以把对象的基引用想像成一个自动创建的隐藏属性.当访问对象的一个属性时,首先查找对象本身,找到则返回;若不,则查找基引用指向的对象的属性(如果还找不到实际上还会沿着原型链向上查找,直至到根).只要没有被覆盖的话,对象原型的属性就能在所有的实例中找到.
原型默认为Object的新实例,由于仍是对象,故可以给该对象添加新的属性:
//prototype默认为newObject();为了方便,记为p_obj
Person.prototype.sayName=function(){
varjohn=newPerson("John");//john的basereference指向p_obj
vareric=newPerson("Eric");//eric的basereference也是指向p_obj
//注意sayName代码中的this将指向实例化后的对象(this绑定)
john.sayName();//john对象本身没有sayName属性,于是访问原型对象p_obj的sayName属性
eric.sayName();//访问同一个原型对象p_obj的sayName属性
//于这个运行点,p_obj已经被修改
//根据上述属性访问流程,新的修改(boss属性)能反映到所有的实例,包括已经创建和即将创建的
alert("John'sbossis"+john.boss);
alert("Eric'sbossis"+eric.boss);
//hisCar和sayCar属性将增加到john对象而不是p_obj对象..
alert(this.name+"hasacarof"+this.hisCar);
//..因此下一句将错误,因为eric对象本身和原型p_obj都没有sayName属性
除了能修改prototype指向的对象,还能修改prototype指向哪一个对象,即为prototype赋予一个不同的对象.这可以实现一种简单的继承:
Superman.prototype.sayHello=function(){
//为prototype赋予Superman的实例..
SupermanCan.prototype=newSuperman();
SupermanCan.prototype.sayMore=function(){
this.sayHello();//调用"父类"的方法
vardavid=newSupermanCan("fly");
//output:I'masuperman.Icanfly
如果先实例化出一个对象,再为构造函数prototype赋予一个不同的对象,将会:已经创建的对象的基引用不变,将来创建的对象的基引用为新的原型对象:
varf1={echo:function(){alert("sound");}};
varfoo=newFoo();//foo的基引用指向Object实例
/*未定义,因为这是"foo对象自己或者基引用指向的对象有echo属性吗?"
而不是"foo对象自己或者Foo.prototype指向的对象有echo属性吗?"*/
varfoo2=newFoo();//foo2的基引用指f1对象
所有的构造函数的prototype都不能为空,就是说Superman.prototype= null会被解释引擎无视;另一方面, Object构造函数也有prototype属性(该属性是只读的,可以为原型增加属性,但不能赋予不同的对象),故因此可以有多层的原型链,但原型链的根必定会是Object.prototype.这意味着给Object.prototype增加属性影响到所有对象:
Object.prototype.echo=function(){
//echo属性将增加到所有对象固有对象和自定义对象
构造函数是一个地地道道的函数,一个函数之所以能成为构造函数,是因为new运算符:
vartest=newTest();//undefined.因为test对象没有定义msg属性
二者区别在于如何切入对象: Test()在某个对象(例子中为window)的上下文上执行代码,即this指向这个对象; new Test()创建一个新对象,并以这个新的对象为上下文(this指向新对象)执行代码,然后返回这个新对象.
结合以上的所有论述,可以推测new Test()行为的伪代码表示为:
temp.(base reference)= Test.prototype;//这一句先于代码体执行,意味着构造函数里的this.xxx能访问原型对象的属性xxx
bind: this=temp;//将this绑定到temp对象
this.wish= dummy;//为temp对象添加wish属性
这个未必会符合内部的二进制实现,但却能很好地解释了JavaScript的特性.