理解JavaScript构造函数
发布时间:2017-11-01, 16:25:57 分类:HTML | 编辑 off 网址 | 辅助
图集1/4
正文 5934字数 441,695阅读
//构造函数
//使自己的对象多次复制,同时实例根据设置的访问等级可以访问其内部的属性和方法
//当对象被实例化后,构造函数会立即执行它所包含的任何代码
function myObject(msg){
//特权属性(公有属性)
this.myMsg = msg; //只在被实例化后的实例中可调用
this.address = '上海';
//私有属性
var name = '豪情';
var age = 29;
var that = this;
//私有方法
function sayName(){
alert(that.name);
}
//特权方法(公有方法)
//能被外部公开访问
//这个方法每次实例化都要重新构造而prototype是原型共享,所有实例化后,都共同引用同一个
this.sayAge = function(){
alert(name); //在公有方法中可以访问私有成员
}
//私有和特权成员在函数的内部,在构造函数创建的每个实例中都会包含同样的私有和特权成员的副本,
//因而实例越多占用的内存越多
}
//公有方法
//适用于通过new关键字实例化的该对象的每个实例
//向prototype中添加成员将会把新方法添加到构造函数的底层中去
myObject.prototype.sayHello = function(){
alert('hello everyone!');
}
//静态属性
//适用于对象的特殊实例,就是作为Function对象实例的构造函数本身
myObject.name = 'china';
//静态方法
myObject.alertname = function(){
alert(this.name);
}
//实例化
var m1 = new myObject('111');
//---- 测试属性 ----//
//console.log(myObject.name); //china
//console.log(m1.name); //undefined, 静态属性不适用于一般实例
//console.log(m1.constructor.name); //china, 想访问类的静态属性,先访问该实例的构造函数,然后在访问该类静态属性
//console.log(myObject.address); //undefined, myObject中的this指的不是函数本身,而是调用address的对象,而且只能是对象
//console.log(m1.address); //上海 此时this指的是实例化后的m1
//---- 测试方法 ----//
//myObject.alertname(); //china,直接调用函数的类方法
//m1.alertname(); //FF: m1.alertname is not a function, alertname 是myObject类的方法,和实例对象没有直接关系
//m1.constructor.alertname(); //china, 调用该对象构造函数(类函数)的方法(函数)
//m1.sayHello(); //hello everyone, myObject类的prototype原型下的方法将会被实例继承
//myObject.sayHello(); //myObject.sayHello is not a function,sayHello是原型方法,不是类的方法
//---- 测试prototype ----//
//console.log(m1.prototype); //undefined, 实例对象没有prototype
//console.log(myObject.prototype); //Object
//alert(myObject.prototype.constructor); //console.log返回myObject(msg),此时alert()更清楚,相当于myObject
//console.log(myObject.prototype.constructor.name); //china, 相当于myObject.name;
Run code
Cut to clipboard
javascript 类属性、类方法、类实例、实例属性、实例方法、prototype、__proto__ 测试与小结
<script>
function Circle( radius ){
this.r = radius;
this.des = "圆形";
this.showInfo = function(){
alert("这是一个"+this.des);
}
}
function Circle_area(r){ return Circle.PI*this.r*this.r; }
function Circle_perimeter(r){ return 2*Circle.PI*r;}
Circle.PI = 3.14;
Circle.perimeter = Circle_perimeter;
Circle.prototype.area = Circle_area;
var c = new Circle(3);
//测试类属性
//alert(Circle.PI )//3.14
//alert(c.PI)//undefined 因为类属性是和类本身,也就是函数本身相关联的,和类实例没有直接关系。
//alert(c.constructor.PI)//3.14 如果想通过类实例访问类属性,那么就需要先访问该实例的构造函数,进而访问该类属性
//alert(Circle.des)//undefined 因为函数Circle函数中的this.des中的this指代的不是函数本身,而是调用r的对象,而且只能是对象。
//alert(c.des)//圆形 this此时为实例化的 对象c。
/*结论:
面向对象的角度:类属性是类对象的直接属性,且该属性与基于该类对象生成的实例对象没有直接关系,无法直接调用。
可以直接通过 类名.属性名 调用该类属性。如果想通过该类对象的实例对象调用类属性,那么可以使用 对象实例.constructor属性
调用该对象的类对象,然后通过类对象调用其类属性
javascript函数角度:类属性是javascript函数对象的直接属性变量(这里之所以称之为属性变量是由于javascript变量和属性的同一
性),且该属性变量与基于该函数对象构造出来的对象引用(生成了一个对象,这个对象实际上是一个空对象,并且保存了对构造
函数以及构造函数初始化时函数内部this关键字下的相关属性和函数的引用[c.prototype和构造函数中this.下面的相关属性、函数]:)
没有直接关系,如果想通过基于构造函数生成的对象c调用构造函数对象的属性变量PI,那么需要通过c.constructor属性找到该构造
函数对象,并通过该对象获取其属性变量。
*/
//测试类方法
//alert(Circle.perimeter(3)); //18.4 直接调用函数的类方法。
//alert( c.perimeter(3) ); //FF:c.perimeter is not a function IE:对象或属性不支持此方法。因为perimeter函数是Circle类的类方法,和实例对象没有直接关系
//alert(c.constructor.perimeter(3));//18.84 调用该对象构造函数(类函数)的方法(函数)。
//alert(c.area(3))//28.25.... Circle类的prototype原型属性下的area方法将会被Circle类的实例对象继承。
//alert(Circle.area(3));//FF: 错误: Circle.area is not a function 因为area方法是Circle类的原型属性的方法,并不是Circle类的直接方法。
//结论:同上,把属性换成了方法,把属性变量换成了函数。
//测试prototype对象属性
//alert(c.prototype); //undefined 实例对象没有ptototype属性
//alert(Circle.prototype); //object Object
//alert(Circle.prototype.constructor)//返回Circle的函数体(函数代码体),相当于alert(Circle)
//alert(Circle.prototype.area(3));//NaN 方法调用成功,但是返回结果却是NaN,原因是area函数内部的this.r是undefined。
//alert(Circle.prototype.PI) //undefined因为PI属性是Circle类函数的直接属性,并不会在prototype属性下存在
//alert(Circle.prototype.constructor.PI)//3.14 通过Circle类的原型对象调用该原型对象的构造函数(类函数),再通过类函数调用PI属性。
/*结论:prototype原型对象是javascript基于原型链表实现的一个重要属性。
Javascript角度:1. 实例对象没有prototype属性,只有构造函数才有prototype属性,也就是说构造函数本身保存了对prototype属性
的引用。。2. prototype属性对象有一个constructor属性,保存了引用他自己的构造函数的引用(看起来像是个循环:A指向B,B指向A...)
3.prototype对象(不要被我这里的属性对象,对象,对象属性搞晕乎了,说是属性对象,就是说当前这个东西他首先是某个对象的属性,
同时自己也是个对象。对象属性就是说它是某个对象的属性。)的属性变量和属性对象将会被该prototype对象引用的构造函数所创建的
对象继承(function A(){} A.prototype.pa = function(){} var oA = new A(); 那么oA将会继承属性函数pa)。
*/
/*这里对 对象属性,对象方法不再做详细测试。
1.javascript对象实例的在通过其构造函数进行实例化的过程中,保存了对构造函数中所有this关键字引用的属性和方法的引用(这里不
讨论对象直接量语法的情况)。但如果构造函数中没有通过this指定,对象实例将无法调用该方法。2.javascript可以通过构造函数创建
多个实例,实例会通过__proto__属性继承原型对象的属性和方法。如果对实例对象的属性和方法进行读写操作,不会影响其原型对象的
属性和方法,也就是说,对于原型对象,javascript实例对象只能读,不能写。那当我们对实例对象的属性和方法进行修改的时候也可以
改变其值这是为什么呢?其实当我们试图在实例对象中使用继承自原型对象的属性或方法的时候,javascript会在我们的实例对象中复
制一个属性或方法的副本,这样,我们操作的时候,其实操作的就是实例对象自己的属性或方法了。
*/
//测试__proto__属性
//alert(c.__proto__)//FF:object IE8:undefined 该属性指向Circle.prototype,也就是说调用该对象将会返回Circle的prototype属性。
//由于IE8及以下版本不支持__proto__属性,所以以下结果都在FF下得出。
//alert(c.__proto__.PI)//undefined 因为函数原型下面没有PI属性,PI是类函数Circle的直接属性
//alert(c.__proto__.area(3))//NaN 该函数执行成功,返回值为NaN是由于函数体中的this.r为undefined。
/*结论:__proto__属性保存了对创建该对象的构造函数引用prototype属性的引用,也就是说构造函数可以引用prototype,基于该构
造函数生成的实例也可以引用,只不过引用的方式不一样。*/
</script>
Run code
Cut to clipboard
参考资料
【JavaScript】JS_Object跟Function的区别
深入理解javascript原型和闭包(6)——继承
JavaScript对象模型-执行模型
(支付宝)给作者钱财以资鼓励 (微信)→
有过 4 条评论 »
这是基于原型链实现继承中对属性和方法的访问,例子如下:
var Person=function(){}; //将函数原型的属性重写为stu和sayHello。 Person.prototype={ Stu:{Name:"Bill"}, sayHello:function(){ alert("Hello,I'm "+this.Stu.Name) } } //实例化 var p1=new Person(); //实例本身没有sayHello方法,所以调用了原型链上的sayHello方法。 p1.sayHello();//Hello.I'm Bill
但当我们修改实例对象的属性和方法时,如果此对象的该属性或方法不存在,那么会在该对象上创建该属性,而不是修改原型链上的属性,例子如下:
//实例化p2 var p2=new Person(); p2.Stu = "student"; alert(p1.Stu); //[Object object] 实例p1依然访问原型链上的Stu属性,并且原型链上的Stu属性并未被p2.Stu = "student"所影响。 alert(p2.Stu);//student 实例p2访问的Stu已经是自己的Stu属性了,p2.Stu = "student"使p2在自己的对象上创建了Stu属性。 //但是,有这样一种情况: delete p2.Stu;//删除实例p2的Stu属性。 alert(p2.Stu);//[Object object] 说明p2自身的Stu属性已被删除,此时访问的已经是原型链上的Stu属性了。 //更改实例对象在原型链上的属性,如果按照前面的说法,p2可能会创建自己的Stu.Name属性并赋值为Martin。但实际情况是这样的,如果实例对象要修改自身的一级属性(p2.Stu = "xxx";),并且该属性不存在,那么,会在该实例对象上创建Stu属性,并修改该属性,此时,不会影响对原型链上的该属性造成影响。如果实例对象要修改自身的N(N>1)级属性(p2.Stu.Name = "xxx";),若该实例不存在N级前的N-1级中的某个属性,就会到原型链上查找该属性,若未找到,查找下一级原型链,如果到最后都没有找到,给出TypeError错误,如果找到了,那么就在该原型链上修改此属性,例子如下: p2.Stu.Name="Martin";//自身不存在Stu属性,访问原型链,原型链上存在,修改该原型链上的该属性,当然,这里只是一级原型链,也可能是二级或者N级。 p1.sayHello();//Martin 说明原型链已被修改 p2.sayHello();//Martin 说明原型链已被修改 p2.Teacher.Name = "jack";// TypeError: Cannot set property 'Name' of undefined 实例和原型链上都不存在Teacher属性,无法赋值。
原因:
根据ECMA262-3 ,当对对象O(所有javascript对象和部分宿主对象)的属性P设置值时会调用内置的[[Put]]方法:
8.6.22[[Put]]方法描述:
1.以名字 P 调用 O 的[[CanPut]]方法。
2.如果 Result(1) 为 false,返回。
3.如果 O 没有以 P 为 名的属性,转到步骤6.
4.将该属性的值设为 V 。不改变该属性的特征。
5.返回。
6.创建以 P 为名属性,将其值设为 V , 并给予它空特征。
7.返回。
不过,值得注意的是,假如 O 是一个 Array 对象,关于[[Put]]方法有更多详细叙述(15.4.5.1)。
8.6.2.3 [[CanPut]]方法执行步骤:
1.如果 O 没有以 P 命 名的属性,转到步骤4.
2.如果该属性有 ReadOnly 特征,返回 false。
3.返回 true。
4.如果 O 的[[Prototype]]为 null,返回 true。
5.以属性名 P 调用 O 的[[Prototype]]的[[CanPut]]方法。
6.返回 Result(5)。
ECMA的解释印证了我们前面例子的现象。
function myObject(msg){ } myObject.name='china'; myObject.nameThis='china'; alert(myObject.name); //IE: china FF:myObject alert(myObject.nameThis); //IE,FF: china
1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例。但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propotype chains)实现的。但在ES6中引入了类(class)这个概念,作为对象的模板,新的class写法知识让原型对象的写法更加清晰,这里不重点谈这个
2.首先我们来详细了解下什么是构造器
构造函数的特点:
a:构造函数的首字母必须大写,用来区分于普通函数
b:内部使用的this对象,来指向即将要生成的实例对象
c:使用New来生成实例对象
eg1:
function Person(name,age){ this.name = name; this.age = age; this.sayHello = function(){ console.log(this.name +"say hello"); } } var boy = new Person("bella",23); boy.sayHello(); // bella say hello
构造函数的缺点:
所有的实例对象都可以继承构造器函数中的属性和方法。但是,同一个对象实例之间,无法共享属性
解决思路:
a:所有实例都会通过原型链引用到prototype
b:prototype相当于特定类型所有实例都可以访问到的一个公共容器
c:那么我们就将重复的东西放到公共容易就好了
eg2:
function Person(name,age){ this.name = name; this.age = age; this.sayHello = function(){ console.log(this.name + "say hello"); } } var girl = new Person("bella",23); var boy = new Person("alex",23); console.log(girl.name); //bella console.log(boy.name); //alex console.log(girl.sayHello === boy.sayHello); //false
一个构造函数Person生成了两个对象实例girl和boy,并且有两个属性和一个方法。但是sayHello方法是不一样的。如上图(图画得很丑)。也就是说当New一个实例对象的时候,都会去创建一个sayHello方法,这就浪费了内存资源,因为sayHello方法使一样的行为的,完全可以被两个实例对象共享。
所以,缺点就是:同一个构造函数的对象实例之间无法共享属性和方法。
为了解决构造函数的这个缺点,js提供了prototype属相来解决该问题。
propotype属性的作用
js中每个数据类型都是对象,除了null 和 undefined(这个可以参考另一篇将null 和 undefined的博客),而每个对象都是继承自一个原型对象,只有null除外,它没有自己的原型对象,最终的Object的原型为null
eg3:
function Person(name,age){ this.name = name; this.age = age; } Person.propotype.sayHello = function(){ console.log(this.name + "say hello"); } var girl = new Person("bella",23); var boy = new Person("alex",23); console.log(girl.name); //bella console.log(boy.name); //alex console.log(girl.sayHello === boy.sayHello); //true
function Person(name,age){ this.name = name; this.age = age; } Person.propotype.sayHello = function(){ console.log(this.name + "say hello"); } var girl = new Person("bella",23); console.log(girl.construcotr); //Person() console.log(girl.construcotr == Person.propotype.construcotr); //true
constructor属性的作用
a:分辨原型对象到底是哪个构造函数
function Person(){}; var person1 = new Person(); console.log(person1.construcotr === Person); //true
b:从实例新建另一个实例
function Person(){}; var person1 = new Person(); var person2 = new person1.construcotr(); console.log(person2 instanceof Person); //true
c:由于constructor属性是一种原型对象和构造函数的关系,所以在修改原型对象的时候,一定 要注意construtor的指向问题,避免instanceof失真,关于这一点,会在继承中讲到。
3.了解了构造器,我们来看下原型prototype
JS中万物都是对象,但是对象也分为:普通对象和函数对象,也就是Object 和 Function.
那么怎么区分普通对象和函数对象呢? ---凡是通过New Function()创建的对象都是函数对象,其他的都是普通对象.
需要注意的是:普通对象没有propotype(prototype即是属性也是对象),但是有__proto__属性。
js创建对象的时候都有一个__propo__内置属性,用于指向创建它的函数对象的原型对象prototype。
我们还是来根据eg3的代码来分析原型链
console.log(girl.__proto__ === Person.protype);//true
console.log(Persion.propotype.__proto__ === Object.propotype);//true
console.log(Object.porpotype.__proto__); //null
通过__proto__串起来直到Object.propotype.__proto__为null的链叫做原型链(矩形表示函数对象,椭圆形表示普通对象)
也许看到这个图会有几个疑问
a:为什么Object.__proto__指向Function.prototype?
Object是函数对象,是通过new Function()创建的,所以...
b:Function.__proto__ === Function.prototype //true
Function也是对象函数,通过new Function()创建,所以...
认识函数对象(Function Object) 可以用function关键字定义一个函数,对于每个函数可以为其指定一个函数名,通过函 数名来进行调用。这些都是代码给用户的印象,而在JavaScript解释执行的时候,实际上每 个函数都是被维护为一个对象,这就是本小节将要介绍的函数对象(Function Object)。 函数对象与其它用户所定义的对象有着本质的区别,这一类对象被称之为内部对象,例 如日期对象(Date)、数组对象(Array)、字符串对象(String)都是属于内部对象。换句话 说,这些内置对象的构造器是由JavaScript本身所定义的:通过执行new Array()这样的语句 返回一个对象,JavaScript 内部有一套机制来初始化返回的对象,而不是由用户来指定对象 的构造方式。 在 JavaScript中,函数对象对应的类型是Function,正如数组对象对应的类型是Array, 日期对象对应的类型是Date一样,可以通过new Function()来创建一个函数对象,也可以通 过function关键字来创建一个对象。为了便于理解,将函数对象的创建和数组对象的创建来 比较。先看数组对象:下面两行代码的作用是一样的,都是创建一个数组对象myArray: var myArray=[]; //等价于 var myArray=new Array(); 同样,下面的两段代码也是等价的,都是创建一个函数myFunction: function myFunction(a,b){ return a+b; } //等价于 var myFunction=new Function("a","b","return a+b"); 现在上面的代码还有些难以理解,但是通过和构造数组对象语句的比较,可以清楚的看 到函数的对象本质,前面介绍的函数声明是上述代码的第一种方式,而在解释器内部,当遇 到这种语法时,就会自动构造一个Function 对象,将函数作为一个内部的对象来存储和运 行。从这里也可以看到,一个函数对象名称(函数变量)和一个普通变量名称具有同样的规 范,都可以通过变量名来引用这个变量,但是函数变量名后面可以跟上括号和参数列表来进 行函数调用。 也许不会有人通过new Function()的形式来创建一个函数,因为一个函数体通常会有多 条语句,如果将它们以一个字符串的形式作为参数传递,那么代码的可读性会非常的差。下 面介绍一下其使用语法: var funcName=new Function(p1,p2,...,pn,body); 参数的类型都是字符串,p1 到pn表示所创建函数的参数名称列表,body表示所创建函 数的函数体语句,而funcName就是所创建函数的名称了。可以不指定任何参数创建一个空 函数,不指定funcName创建一个无名函数,当然那样的函数什么用处都没有。 需要注意的是,前面说p1 到pn是参数名称的列表,这意味着p1不仅仅只能代表一个 参数,它也可以是一个逗号格开的参数列表,例如下面的定义是等价的: new Function("a", "b", "c", "return a+b+c") new Function("a, b, c", "return a+b+c") new Function("a,b", "c", "return a+b+c") JavaScript引入Function类型并提供new Function()这样的语法来创建函数并不是毫无意 义的,在后面可以看到,函数作为一个对象,它本身就可以具有一些方法和属性,而为函数 对象添加属性和方法就必须借助于Function这个类型。 现在已经认识到了函数的本质,它其实是一个内部对象,由JavaScript解释器决定其运 行方式。通过上述代码创建的函数,在程序中可以使用函数名进行调用。于是在本节开头列 出的函数定义问题也得到了解释:它们都是创建函数对象的正确语法。注意直接在函数声明 后面加上括号就表示创建完成后立即进行函数调用,例如: var i=function (a,b){ return a+b; }(1,2); alert(i); 这段代码会显示变量i 的值等于3。i 是表示返回的值,而不是创建的函数,因为括号 “(”比等号“=”有更高的优先级。这样的代码可能并不常用,但当用户想在很长的代码段 中进行模块化设计或者想避免命名冲突,这是一个不错的解决办法。 需要注意的是,尽管下面两种创建函数的方法是等价的: function funcName(){ //函数体 } //等价于 var funcName=function(){ //函数体 } 但前面一种方式创建的是有名函数,而后面是创建了一个无名函数,只是让一个变量指 向了这个无名函数。在使用上仅有一点区别,就是:对于有名函数,它可以出现在调用之后 再定义;而对于无名函数,它必须是在调用之前就已经定义。例如: <script language="JavaScript" type="text/javascript"> <!-- func(); var func=function(){ alert(1) } //--> </script> 这段语句将产生func未定义的错误,而: <script language="JavaScript" type="text/javascript"> <!-- func(); function func(){ alert(1) } //--> </script> 则能够正确执行,甚至下面的语句也能正确执行: <script language="JavaScript" type="text/javascript"> <!-- func(); var someFunc=function func(){ alert(1) } //--> </script> 由此可见,尽管JavaScript是一门解释型的语言,但它会在进行函数调用时,检查整个 代码中是否存在相应的函数定义,这个函数名只有是通过function funcName()形式定义的才 会有效,而不能是匿名函数。