在JavaScript中this表示:谁调用当前函数this就指向谁,不知道调用者时this指向window。javascript
JavaScript是由对象组成的,一切皆为对象,万物皆为对象。this是一个动态的对象,根据调用的对象不一样而发生变化,固然也可使用call、apply修改this指向的对象。它表明函数运行时,自动生成的一个内部对象,只能在函数内部使用html
代码以下:java
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript this</title> </head> <body> <h2>JavaScript this</h2> <input type="button" value="按钮A" id="btnA"/> <input type="button" value="按钮B" id="btnB" onclick="sayHello()"/> <script type="text/javascript"> function sayHello(){ alert("Hello Button!"); console.log(this); }; document.getElementById("btnA").onclick=sayHello; </script> </body> </html>
请问输出到控制台的结果是什么?相同吗?输出两个什么对象?node
答案:git
<input type="button" value="按钮A" id="btnA"/> Window
在面向过程的语言中咱们习惯把完成某个特定功能的代码块称为“函数”或“过程”,固然过程通常没有返回值。在面向对象语言中咱们把对象的功能称为“方法”。但JavaScript是种介于面向对象与面向过程当中间的语言,一样的方法有时是函数,有时是方法,以下所示:github
<script type="text/javascript"> //1 function show(){ console.log("这是一个函数"); } //2 (function(){ console.log("这是一个函数表达式"); })(); //3 var obj1={ show:function(){ console.log("这是一个方法"); } }; //4 function obj2(){ //obj2是函数,构造函数 this.show=function(){ console.log("这是一个方法,是一个表达式"); } } var obj3=new obj2(); </script>
能够简单的认为若是调用时没有经过对象没有指定上下文则为函数,不然为方法。chrome
当在所有范围内使用 this,它将会指向全局对象。通常是window对象,但全局对象不必定只有window,特别是在node.js环境中。做为函数调用时通常指向全局对象。设计模式
<script type="text/javascript"> var i=5; i=6; console.log(i); //6 console.log(window.i); //6 console.log(this.i); //6 console.log(this); //window </script>
结果:数组
<script type="text/javascript"> var name="tom"; console.log(this.name); //顶层对象,通常为window function show() { console.log(this.name); //顶层对象,通常为window return function(){ console.log(this.name); //顶层对象,通常为window,由于返回的是函数 } } var f1=show(); f1(); </script>
运行结果:浏览器
当函数做为方法调用时this指向调用该方法的对象。
function show() { //当obj1.view()时this就是obj1,obj2.view()时this就是obj2 console.log(this.name); } var obj1={name:"jack",view:show}; obj1.view(); var obj2={name:"mark",view:show}; obj2.view();
运行结果:
示例代码:
<script type="text/javascript"> var name="lucy"; this.name="Mali"; function show() { //当obj1.view()时this就是obj1,obj2.view()时this就是obj2 console.log(this.name); return function(){ console.log(this.name); //这里的this是调用时动态决定 } } var obj1={name:"jack",view:show}; obj1.view(); var obj2={name:"mark",view:show}; var f1=obj2.view(); f1(); //由于f1属于window(浏览器环境),调用f1时的this也就是window </script>
运行结果:
示例代码:
<script type="text/javascript"> var name="lucy"; this.name="Mali"; function show() { //当obj1.view()时this就是obj1,obj2.view()时this就是obj2 console.log(this.name); that=this; //闭包 return function(){ console.log(that.name); //这里的that指向的是外层函数调用时的对象 } } var obj1={name:"jack",view:show}; obj1.view(); var obj2={name:"mark",view:show}; var f1=obj2.view(); f1(); </script>
运行结果:
构造函数中的this指向新建立的对象,new出谁this就是谁。
示例代码:
<script type="text/javascript"> this.name="吉娃娃"; function Dog(name) { this.name=name; this.bark=function(){ console.log(this.name+"在叫,汪汪..."); } } var dog1=new Dog("哈士奇"); dog1.bark(); </script>
运行结果:
按照严格的语法,构造函数不该该返回值,可是JavaScript是容许构造方法返回值的,默认返回this,修改后的示例以下:
this.name="吉娃娃"; function Dog(name) { this.name=name; this.bark=function(){ console.log(this.name+"在叫,汪汪..."); } return this.bark; } var dog1=new Dog("哈士奇"); dog1();
运行结果:
当调用方法是经过Function.call,或Function.apply时this为调用时指定的对象,若是没有指定则为顶层对象,浏览器中是window。
示例代码:
this.name="伽啡"; function Cat(name) { this.name=name; } var bark=function(n){ console.log(this.name+"叫了"+(n||1)+"声喵喵..."); } var cat1=new Cat("波斯"); var cat2=new Cat("龙猫"); bark.call(cat1,5); //调用时this是cat1 bark.apply(cat2,[8]); //调用时this是cat2 bark.apply(window,[9]); //调用时this是window bark.apply(); //调用时this是顶层对象
运行结果:
若是页面中的元素与事件进行绑定时事件中的this通常指向网页元素,如按钮,文本框等。可是在元素中直接指定要执行的函数则this指向window顶层对象。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>this</title> </head> <body> <input type="button" value="按钮A" class="btn"/> <input type="button" value="按钮B" class="btn"/> <input type="button" value="按钮C" class="btn"/> <input type="button" value="按钮D" onclick="handler()"/> <!--handler中的this指向window或顶层对象--> <input type="button" value="按钮E" id="btnE"/> <script type="text/javascript"> var buttons = document.querySelectorAll(".btn"); for (var i=0;i<buttons.length;i++) { //handler中的this指向按钮对象 buttons[i].onclick=handler; } function handler(){ alert(this.value); } //handler中的this指向btnE document.getElementById("btnE").addEventListener("click",handler,false); </script> </body> </html>
当点击A-C时的效果:
当点击D时的效果:
当点击按钮E时的效果:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>this</title> </head> <body> <input type="button" value="按钮F的值" id="btnF" /> <input type="button" value="按钮G的值" id="btnG" onclick="app.show()"/> <script type="text/javascript"> value="window的值" var app = { value: "app的值", show: function() { alert(this.value); //若是show为事件,则this指向触发事件的对象 }, init: function() { //handler中的this指向btnE document.getElementById("btnF").addEventListener("click", this.show, false); } }; app.show(); //"app的值",show方法中的this指向app这个对象 app.init(); //init中的this指向谁app对象 </script> </body> </html>
加载时运行结果:
点击按钮F时的效果:
点击按钮G时的效果:
在HTML元素上直接指定事件严格来讲都不能说是事件绑定,只能描述是当按钮点击完成后执行的函数。若是想将执行时的对象带回,能够增长参数this。
函数调用能够以下几种基本形式:
1)、fun(x,y);
2)、obj.fun(x,y);
3)、fun.apply(obj,[x,y]);
4)、fun.call(obj,x,y);
第1,2种调用的方式最终将转换成3,4方法,也就是说1,2只是一种简化的语法糖,那么this就是apply与obj的第1个参数,指向调用时的上下文。
在javascript面向对象中关于原型(prototype)、原型链、__proto__、Function、Object等内容是较难理解的,先上几张便于你们理解的图:
JavaScript藏宝图
在JavaScript中,原型也是一个对象,经过原型能够实现对象的属性继承,JavaScript的对象中都包含了一个"Prototype"内部属性,这个属性所对应的就是该对象的原型。
"Prototype"做为对象的内部属性,是不能被直接访问的。因此为了方便查看一个对象的原型,Firefox和Chrome中提供了"__proto__"这个非标准(不是全部浏览器都支持)的访问器(ECMA引入了标准对象原型访问器"Object.getPrototype(object)")。
(1)、全部构造器/函数的__proto__都指向Function.prototype,它是一个空函数(Empty function)
(2)、全部对象的__proto__都指向其构造器的prototype
(3)、对于全部的对象,都有__proto__属性,这个属性对应该对象的原型
(4)、对于函数对象,除了__proto__属性以外,还有prototype属性
当一个函数被用做构造函数来建立实例时,该函数的prototype属性值将被做为原型赋值给全部对象实例(也就是设置实例的__proto__属性)
实例说明:
代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //构造器,函数,对象 function Cat(nickname, color) { //各个对象独有的 this.nickname = nickname; this.color = color; } //全部对象公有的 Cat.prototype.life = 9; Cat.prototype.bark = function() { console.log(this.nickname, this.color, this.life, "喵喵..."); } var c1 = new Cat("波斯", "绿色"); c1.bark(); var c2 = new Cat("加菲", "红色"); c2.bark(); </script> </body> </html>
控制台:
关系图:
(1)、全部的函数/构造器都拥有prototype属性,用于指向原型对象。
(2)、全部的函数便是构造器又是对象。
(3)、全部的对象都拥有__proto__非标准属性。
(4)、全部函数(构造器)的__proto__都指向Function.prototype对象。
(5)、全部对象的__proto__指向它的构造器的prototype对象。
(6)、全部函数都是由Function构造出来的,Function本身构造了本身,Object是由Function构造出来的,Function是构造器。
(7)、全部对象的最终原型对象都指向了Object的prototype属性,Object的prototype对象的__proto__属性指向NULL。
(8)、全部prototype原型对象中的constructor属性都指向其构造器。
(9)、原型对象prototype中的成员是全部对象共有的。
(10)、对象在查找成员时先找本对象本身的全部成员,再查找构造器的原型中的成员(向上再查找父类的成员,多步),最终查询Object的成员。
JavaScript Object Layout:
Object Relationship Diagram with Inheritance
JavaScript是一种经过原型实现继承的语言与别的高级语言是有区别的,像java,C#是经过类型决定继承关系的,JavaScript是的动态的弱类型语言,总之能够认为JavaScript中全部都是对象,在JavaScript中,原型也是一个对象,经过原型能够实现对象的属性继承,JavaScript的对象中都包含了一个" prototype"内部属性,这个属性所对应的就是该对象的原型。
"prototype"做为对象的内部属性,是不能被直接访问的。因此为了方便查看一个对象的原型,Firefox和Chrome内核的JavaScript引擎中提供了"__proto__"这个非标准的访问器(ECMA新标准中引入了标准对象原型访问器"Object.getPrototype(object)")。
如今有一个Student构造函数,经过new调用该构造函数能够建立一个新的对象,示例以下:
//构造方法,用于建立学生对象 function Student(name) { this.name = name; } var tom=new Student("tom"); tom.show=function(){ console.log(this.name); } var rose=new Student("rose"); rose.show=function(){ console.log(this.name); } tom.show(); rose.show();
运行结果:
上面的示例中tom与rose都须要show方法,建立对象后咱们直接为两个对象分别都增长了show方法,可是这样作的弊端是若是再增长更多对象也须要添加show方法,最好的办法是修改构造方法Student,以下所示:
//构造方法,用于建立学生对象 function Student(name) { this.name = name; this.show = function() { console.log(this.name); } } var tom = new Student("tom"); var rose = new Student("rose"); tom.show(); rose.show();
可是若是Student构造函数是一个内置的函数或是其它框架定义的类型,则修改就比较麻烦了,能够不修改源码的状况扩展该对象的原型也能够达到目的,以下所示:
//构造方法,用于建立学生对象 function Student(name) { this.name = name; } //修改Student对象的原型,增长show方法 Student.prototype.show = function() { console.log(this.name); } var tom = new Student("tom"); var rose = new Student("rose"); tom.show(); rose.show();
在示例中咱们并无修改Student这个构造函数而是修改了了Student的原型对象,相似它的父类,以下图所示:
此时你也许会认为全部的Object都增长了show方法,这样想是错误的,由于Student的原型是一个对象而不是像其它高级语中的类型,咱们修改的只是Student的原型对象,不会影响其它的对象。
var mark=new Object(); mark.name="mark"; var lucy={name:"lucy"}; //mark.show(); //lucy.show();
此处的两个show方法是错误的。总之原型的主要做用就是为了实现继承与扩展对象。
在JavaScript中,原型也是一个对象,经过原型能够实现对象的属性继承,JavaScript的对象中都包含了一个”[[Prototype]]”内部属性,这个属性所对应的就是该对象的原型。
“[[Prototype]]”做为对象的内部属性,是不能被直接访问的。因此为了方便查看一个对象的原型,Firefox和Chrome中提供了__proto__这个非标准(不是全部浏览器都支持)的访问器(ECMA引入了标准对象原型访问器”Object.getPrototype(object)”)。在JavaScript的原型对象中,还包含一个”constructor”属性,这个属性对应建立全部指向该原型的实例的构造函数
在 JavaScript 中,判断一个变量的类型能够用typeof,如:
var str1="Hello"; var str2=new String("Hello"); console.log(typeof 1); console.log(typeof(true)); console.log(typeof str1); console.log(typeof str2); console.log(typeof([1,2,3])); console.log(typeof Date); console.log(typeof undefined); console.log(typeof null); console.log(typeof zhangguo);
运行结果:
1)、数字类型, typeof 返回的值是 number。好比说:typeof(1),返回值是number
2)、字符串类型, typeof 返回的值是 string。好比typeof("123")返回值是string。
3)、布尔类型, typeof 返回的值是 boolean 。好比typeof(true)返回值是boolean。
4)、对象、数组、null 返回的值是 object 。好比typeof(window),typeof(document),typeof(null)返回的值都是object。
5)、函数类型,返回的值是 function。好比:typeof(eval),typeof(Date)返回的值都是function。
6)、不存在的变量、函数或者undefined,将返回undefined。好比:typeof(abc)、typeof(undefined)都返回undefined。
使用 typeof 运算符时采用引用类型存储值会出现一个问题,不管引用的是什么类型的对象,它都返回 "object"。ECMAScript 引入了另外一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符类似,用于识别正在处理的对象的类型。与 typeof 方法不一样的是,instanceof 方法要求开发者明确地确认对象为某特定类型。
instanceof用于判断某个对象是否被另外一个函数构造。
例如:
var oStringObject = new String("hello world");
console.log(oStringObject instanceof String); // 输出 "true"
var str1="Hello"; var str2=new String("Hello"); console.log(str1 instanceof String); //string false console.log(str2 instanceof String); //object true console.log(1 instanceof Number); //false function Foo(){}; var f1=new Foo(); console.log(f1 instanceof Foo);
运行结果:
函数就是对象,表明函数的对象就是函数对象。全部的函数对象是被Function这个函数对象构造出来的。Function是最顶层的构造器。它构造了系统中全部的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。这也代表Function具备自举性(自已构造本身的能力)。这也间接决定了Function的call和constructor逻辑相同。每一个对象都有一个 constructor 属性,用于指向建立其的函数对象。
先来看一个示例:
function Foo(a, b, c) { //Foo这个构造函数由Function构造,函数也是对象 return a * b * c; } var f1=new Foo(); //f1由Foo构造,Foo是一个构造函数,能够理解为类 console.log(Foo.length); //参数个数 3 console.log(typeof Foo.constructor); //function console.log(typeof Foo.call); //function console.log(typeof Foo.apply); //function console.log(typeof Foo.prototype); //object object
运行结果:
函数与对象具备相同的语言地位
没有类,只有对象
函数也是一种对象,所谓的函数对象
对象是按引用来传递的
function Foo() {}; var foo = new Foo(); //Foo为foo的构造函数,简单理解为类型 console.log(foo instanceof Foo); // true //可是Function并非foo的构造函数 console.log(foo instanceof Function); // false //Function为Foo的构造函数 alert(Foo instanceof Function); //true
上面的代码解释了foo和其构造函数Foo和Foo的构造函数Function的关系,Foo是由Function构造获得,能够简单理解为,系统中有一个这样的构造函数:
function Function(name,body) { } var Foo=new Function("Foo","");
如上面例子中的构造函数,JavaScript中函数也是对象,因此就能够经过_proto_查找到构造函数对象的原型。
Function对象做为一个函数,就会有prototype属性,该属性将对应”function () {}”对象。
Function对象做为一个对象,就有__proto__属性,该属性对应”Function.prototype”,也就是说,”Function._proto_ === Function.prototype”。
在这里对“prototype”和“proto”进行简单的介绍:
对于全部的对象,都有__proto__属性,这个属性对应该对象的原型。
对于函数对象,除了__proto__属性以外,还有prototype属性,当一个函数被用做构造函数来建立实例时,该函数的prototype属性值将被做为原型赋值给全部对象实例(也就是设置实例的__proto__属性)
对于Object它是最顶层的对象,全部的对象都将继承Object的原型,可是你也要明确的知道Object也是一个函数对象,因此说Object是被Function构造出来的。
alert(Function instanceof Function);//true alert(Function instanceof Object);//true alert(Object instanceof Function);//true function Foo() {}; var foo = new Foo(); alert(foo instanceof Foo); // true alert(foo instanceof Function); // false alert(foo instanceof Object); // true alert(Foo instanceof Function); // true alert(Foo instanceof Object); // true
JavaScript 原型链
function A() {this.x="x";}; var a=new A(); function B() {this.y="y"}; B.prototype=a; var b=new B(); function C() {this.z="z"}; C.prototype=b; var c=new C(); console.log(c instanceof C); console.log(c instanceof B); console.log(c instanceof A); console.log(c instanceof Object); console.log(c.x+","+c.y+","+c.z);
运行结果:
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 Object.prototype),若是仍然没有找到指定的属性,就会返回 undefined。
Object对象自己是一个函数对象。既然是Object函数,就确定会有prototype属性,因此能够看到”Object.prototype”的值就是”Object {}”这个原型对象。反过来,当访问”Object.prototype”对象的”constructor”这个属性的时候,就获得了Obejct函数。
另外,当经过”Object.prototype._proto_”获取Object原型的原型的时候,将会获得”null”,也就是说”Object {}”原型对象就是原型链的终点了。
JavaScript内置了不少对象,如Array、Boolean、Date、Function、Number、Object、String
假设咱们想给String类型增长一个repeat方法,实现重复字符,如"a".rpt(),则将输出aa,"a".rpt(5),输出“aaaaa”,由于String是JavaScript中内置的对象,能够经过修改该对象的原型达到目的:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>prototype原型</title> </head> <body> <script type="text/javascript"> var v="hi"; String.prototype.rpt=function(n) { n=n||2; var temp=""; for(var i=0;i<n;i++) temp+=this; return temp; } console.log(v.rpt()); console.log(v.rpt(10)); </script> </body> </html>
运行结果:
示例中给String对象的原型增长了一个rpt方法,全部的String都是衍生自String.prototype,那全部的String对象都将得到rpt方法。
//扩展String类型,增长trim方法用于删除字符串的首尾空格 String.prototype.trim=function() { return this.replace(/^\s+|\s+$/igm,''); } console.log("[begin]"+" Hello JavaScript ".trim()+"[end]");
运行结果:
为了扩展更加方便,能够修改Function的原型,给每个函数衍生的对象增长方法method用于扩展。
//修改函数对象的原型,添加method方法,扩展全部的函数 Function.prototype.method=function(name,fun){ //若是当前扩展的函数的原型中不包含名称为name的对象 if(!this.prototype[name]){ //添加 this.prototype[name]=fun; } } String.method("show",function(){ console.log("输出:"+this); }); "Hello".show(); "JavaScript".show();
运行结果:
咱们能够经过对象调用某个方法,由于这个对象的原型中已经定义好了该方法,其实咱们经过原型也能够直接调用某个方法,有些方法只存在原型中,只有当前类型关联了该原型才能够得到该方法,但有时咱们须要使用该方法去处理非该原型下的对象,如:
function add(x,y,z) { var array1=[].slice.call(arguments,0,3); console.log(array1); var array2=Array.prototype.slice.apply(arguments,[0,3]); console.log(array2); } add(1,2,8,9,10);
运行结果:
示例代码:
var str1 = "Hello JavaScript"; console.log(str1.toUpperCase()); //传统的调用办法 var str2=String.prototype.toUpperCase.apply(str1); //经过原形直接调用 console.log(str2); var str3=String.prototype.toUpperCase.call(str1); //经过原形直接调用 console.log(str3); var array1=[2,3,5,7,8,9,10]; var array2=array1.slice(1,3); console.log(array2); var slice=Array.prototype.slice; console.log(slice.apply(array1,[1,3]));//经过原形直接调用 console.log(slice.call(array1,1,3));
运行结果:
练习:扩展Date,增长一个显示中文日期的方法,如:new Date().trandition(),输出2016年08月09日 23点15分18秒;使用prototype的方式间接调用该方法。
prototype是函数才有的属性,prototype自己就是一个对象,prototype对象中拥有constractor构造器(该构造器反向指回函数)
__proto__是对象带有属性,用于访问建立对象的类型所对应的原型
字面量建立的对象(o={})或Object建立的对象__proto__指向Object.prototype
示例:
var a = {}; console.log(a.prototype); //undefined console.log(a.__proto__); //Object {} var b = function(){} console.log(b.prototype); //b {} console.log(b.__proto__); //function() {}
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> //构造方法,类,函数 function Foo(name) { this.name = name; } var f1 = new Foo("zhangguo"); //prototype是函数才有的属性,prototype自己就是一个对象,prototype对象中拥有constractor构造器(该构造器反向指回函数) console.log(f1.prototype); //undefined console.log(Foo.prototype); //function Foo.prototype //__proto__是对象带有属性,用于访问建立对象的类型所对应的原型 console.log(f1.__proto__); //function Foo.prototype console.log(Foo.__proto__); //function(){} //Foo.__proto__ 就是Function.prototype console.log(Foo.__proto__===Function.prototype); //true console.log(Foo.prototype===f1.__proto__); //true //字面量建立的对象(o={})或Object建立的对象__proto__指向Object.prototype var o={}; console.log(o.__proto__===Object.prototype); //true var obj=new Object(); console.log(obj.__proto__===Object.prototype); //true var obj2=Object.create({}); console.log(obj2.__proto__===Object.prototype); //false // console.log(obj1.prototype); // console.log(obj1.__proto__); // console.log(Foo.prototype); </script> </body> </html>
结果:
/*一、字面量方式*/ var a = {}; console.log(a.__proto__); //Object {} console.log(a.__proto__ === a.constructor.prototype); //true /*二、构造器方式*/ var A = function(){}; var a = new A(); console.log(a.__proto__); //A {} console.log(a.__proto__ === a.constructor.prototype); //true /*三、Object.create()方式*/ var a1 = {a:1} var a2 = Object.create(a1); console.log(a2.__proto__); //Object {a: 1} console.log(a.__proto__ === a.constructor.prototype); //false(此处即为图1中的例外状况)
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> //构造方法,类,函数 function Foo(name) { this.name = name; } var f1 = new Foo("zhangguo"); console.log(f1.constructor === Foo); //true console.log(f1.__proto__ === Foo.prototype); //true /*一、字面量方式*/ var a = {}; //a的__proto__是指向了a的构造器所指向的原型 //字面量建立的对象的__proto__指向了Object的原型 console.log(a.__proto__ === a.constructor.prototype); //true /*二、构造器方式*/ var A = function() {}; var a = new A(); console.log(a.__proto__); //A的原型 console.log(a.__proto__ === a.constructor.prototype); //true console.log(a.__proto__ === A.prototype); //true /*三、Object.create()方式*/ var a1 = {a: 1} var a2 = Object.create(a1); console.log(a2.__proto__===a1); //true,a1就是a2的原型(父类) </script> </body> </html>
结果:
示例:
var A = function(){}; var a = new A(); console.log(a.__proto__); //A {}(即构造器function A 的原型对象) console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象) console.log(a.__proto__.__proto__.__proto__); //null
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> //构造方法,类,函数 function Foo(name) { this.name = name; } var f1 = new Foo("zhangguo"); //一、var f1={}; //二、f1.__proto__=Foo.prototype; //三、Foo.call(f1,"zhangguo"); //在Foo的原型中添加属性age Foo.prototype.age = 80; Foo.prototype.show = function() { console.log("name:" + this.name + ",age:" + this.age); } var f2 = new Foo("zhangsan"); console.log(f1.age == f2.age); f1.show(); f2.show(); var A = function() {}; var a = new A(); console.log(a.__proto__); //A {}(即构造器function A 的原型对象) console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象) console.log(a.__proto__.__proto__.__proto__); //null </script> </body> </html>
结果:
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 Object.prototype),若是仍然没有找到指定的属性,就会返回 undefined。
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.MaxNumber = 9999; Person.__proto__.MinNumber = -9999; var will = new Person("Will", 28); console.log(will.MaxNumber); // 9999 console.log(will.MinNumber); // undefined
在这个例子中分别给”Person.prototype “和” Person.proto”这两个原型对象添加了”MaxNumber “和”MinNumber”属性,这里就须要弄清”prototype”和”proto”的区别了。
“Person.prototype “对应的就是Person构造出来全部实例的原型,也就是说”Person.prototype “属于这些实例原型链的一部分,因此当这些实例进行属性查找时候,就会引用到”Person.prototype “中的属性。
对象建立方式影响原型链
var July = { name: "张三", age: 28, getInfo: function(){ console.log(this.name + " is " + this.age + " years old"); } } console.log(July.getInfo());
当使用这种方式建立一个对象的时候,原型链就变成下图了. July对象的原型是”Object.prototype”也就是说对象的构建方式会影响原型链的形式。
在JavaScript中,每一个函数 都有一个prototype属性,当一个函数被用做构造函数来建立实例时,这个函数的prototype属性值会被做为原型赋值给全部对象实例(也就是设置 实例的`__proto__`属性),也就是说,全部实例的原型引用的是函数的prototype属性。(只有函数对象才会有这个属性!)
使用new关键字调用函数(new ClassA(…))的具体步骤:
1. 建立空对象;
var obj = {};
2. 设置新对象的constructor属性为构造函数,设置新对象的__proto__属性指向构造函数的prototype对象;
obj.__proto__ = ClassA.prototype;
3. 使用新对象调用构造函数,函数中的this被指向新实例对象:
ClassA.call(obj); //{}.构造函数();
4. 将初始化完毕的新对象地址,保存到等号左边的变量中
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> function Car(name){ this.name=name; } //一、直接建立 var c1=new Car("BYD"); //二、实际建立对象的步骤 //2.一、建立空的对象字面量 var c2={}; //2.二、将对象的__proto__指向构造器的原型对象(prototype) c2.__proto__=Car.prototype; //2.三、调用构造器,初始化建立的对象 Car.call(c2,"BYD"); //2.四、将建立的对象地址赋值给变量(返回给左边的变量) </script> </body> </html>
结果:
注意:若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象;若返回值是引用类型的值,则实际返回值为这个引用类型。
new 的过程分为三步
var p = new Person('张三',20); 1. var p={}; 初始化一个对象p。 2. p._proto_=Person.prototype;,将对象p的 __proto__ 属性设置为 Person.prototype 3. Person.call(p,”张三”,20);调用构造函数Person来初始化p。
一、prototype是函数才有的属性
二、__proto__是每一个对象都有的属性,它不是W3C的标准属性,它指向prototype
三、__proto__能够理解为“构造器原型”,__proto__===constructor.prototype(Object.create()建立的对象不适用)
四、当js引擎查找对象的属性时,先查找对象自己是否存在该属性,若是不存在会在原型链上查找,但不会查找本身的prototype
五、函数的原型对象constructor默认指向函数自己,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针指向上一层的原型对象,而上一层的原型对象的结构依然相似,这样利用__proto__一直指向Object的原型对象上,而Object的原型对象用Object.prototype.__proto__ = null表示原型链的最顶端,如此变造成了javascript的原型链继承,同时也解释了为何全部的javascript对象都具备Object的基本方法。
function Foo() {} var foo = new Foo(); console.log(foo.prototype);// undefined console.log(foo.__proto__ === Foo.prototype);// true console.log(Foo.prototype);// [object Object] console.log(Foo.prototype.prototype);// undefined
解释
只有函数对象有 prototype 属性(通常对象本身加的不算)
一、 foo 是 Foo 的一个实例,可是不是一个函数,因此没有prototype;Foo是Function的一个实例,而Function是一个函数,他的实例Foo也是一个函数,因此他们都有prototype。此外Object Array RegExp等也是函数。Math就仅仅是一个new Object() ,不是函数。
二、构造函数的prototype,默认状况下就是一个new Object()还额外添加了一个constructor属性。因此说默认是没有prototype只有__proto__的。
除了Object.prototype这个对象,其余全部的对象都会有__proto__属性,以后函数才会有prototype属性。
在建立对象的时候会自动建立一个__proto__属性,指向它构造函数的prototype,当访问这个对象的属性的时候会顺带访问__proto__中对应的属性,也就是构造函数prototype这样实现了继承。
只有建立函数的时候才会建立一个prototype属性,目的就是为了完成上面的继承方式。
图(橙色箭头是初始的关系,绿色是执行var Fish = new Fu...建立,蓝色是执行f1= new Fish()建立。)
这样f1 就能够经过__proto__ 访问 Fish.prototype中的属性(固然这是程序执行的时候自动查找的)。Fish就能够访问 Function.prototype定义的属性。全部对象均可以访问Object.prototype 中的属性。
一、全部的函数/构造器都拥有prototype属性,原型必定是对象
二、全部原型对象拥有constructor属性指回其构造函数,原型中存放全部构造器建立的对象共有成员
三、JavaScript中经过原型链实现继承,将对象与对象串联起来,查询成员时由近到远
四、全部的对象都将指向其构造器的原型对象,默认不容许直接访问,但chrome等浏览器中能够经过__proto__访问到(全部对象的__proto_指向其构造器的原型对象)
五、JavaScript中一切都是对象,函数建立了对象其自己又是对象,对象都指定原型
六、全部的函数都是Function建立出来的,Function本身建立了本身
七、Object函数是Function建立出来的,Object函数的__proto__指向Function的原型对象
八、Function的原型对象的__proto__指向Object的原型对象,Object的原型对象指向了null,这是原型链的尽头
由于在JavaScript中没有访问修饰符,也没有块级做用域,this所定义的属性默认对外就是公开访问的,以下示例:
function Animal(){ this.name="动物"; this.weight=0; //重量 } var cat=new Animal(); cat.name="猫"; cat.weight=-90; console.log(cat);
输出:
weight是-90,不正确,封装:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> function Animal() { this.name = "动物"; var weight = 0; //重量 this.getWeight = function() { //读 return weight; }; this.setWeight = function(_weight) { //写 if(_weight > 0) { weight = _weight; } else { throw "动物的重量必须为正数"; } } } var cat = new Animal(); cat.name = "猫"; try{ cat.setWeight(-90); }catch(e){ console.log(e); } console.log(cat); </script> </body> </html>
结果:
function Animal() { this.name="动物"; this.getName=function(){ return this.name; } this.setName=function(name){ this.name=name; } this.bark=function(){ console.log(this.name+"在叫..."); } } var animal=new Animal(); animal.setName("小狗"); animal.bark();
这里定义的一个构造函数,将name封装成属性,Animal函数自己用于封装动物的属性与行为,运行结果:
JavaScript的继承的实现主要依靠prototype(原型)来实现,再增长两个动物,如狗与猫:
function Animal() { this.name="动物"; this.getName=function(){ return this.name; } this.setName=function(name){ this.name=name; } this.bark=function(){ console.log(this.name+"在叫..."); } } function Dog(){ this.hunt=function(){console.log("狗在捕猎");}; } //指定Dog构造函数的原型对象,简单理解为父类 Dog.prototype=new Animal(); function Cat(){ this.hunt=function(){console.log("猫在抓老鼠");}; } Cat.prototype=new Animal(); var dog=new Dog(); dog.bark(); dog.hunt(); var cat=new Cat(); cat.bark(); cat.hunt();
运行结果:
java与C#中的多态主要体如今重载与重写上,由于JavaScript是弱类型的,类方法参数是动态指定的因此并无真正意义上的重载,只能在方法中判断参数达到目的。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript面向对象</title> </head> <body> <script type="text/javascript"> function Animal() { this.name="动物"; this.getName=function(){ return this.name; } this.setName=function(name){ this.name=name; } this.bark=function(){ console.log(this.name+"在叫..."); } } function Dog(){ this.hunt=function(){console.log("狗在捕猎");}; } //指定Dog构造函数的原型对象,简单理解为父类 Dog.prototype=new Animal(); //重写原型对象中的bark方法 Dog.prototype.bark=function(){ console.log("汪汪..."); } function Cat(){ this.hunt=function(){console.log("猫在抓老鼠");}; } Cat.prototype=new Animal(); //重写原型对象中的bark方法 Cat.prototype.bark=function(){ console.log("喵喵..."); } function play(animal) { animal.bark(); animal.hunt(); } var dog=new Dog(); var cat=new Cat(); play(dog); play(cat); </script> </body> </html>
运行结果:
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //二种重写的办法 function Animal(name) { this.name = name || "动物"; this.bark = function() { console.log(this.name + "叫了:嗷嗷..."); } }; function Cat() { this.hunt = function() { console.log(this.name + "正在捕老鼠!"); } } Cat.prototype = new Animal("猫"); Cat.prototype.bark = function() { console.log("喵喵"); } var cat = new Cat(); cat.hunt(); cat.bark(); function Dog() { this.hunt = function() { console.log(this.name + "正在捕猎!"); } this.bark = function() { console.log("汪汪"); } } //让Dog继承Animal,修改原型的指向 Dog.prototype = new Animal("狗"); var dog = new Dog(); dog.hunt(); dog.bark(); //重载的办法 function add(n) { if(typeof n === "number") { return ++n; } if(n instanceof Array){ var sum=0; for(var i=0;i<n.length;i++){ sum+=n[i]; } return sum; } } console.log(add(100)); console.log(add([1, 2, 3, 4, 5])); </script> </body> </html>
结果:
练习:请使用javascript完成一个简单工厂设计模式。
https://github.com/zhangguo5/javascript003.git
https://git.coding.net/zhangguo5/javascript_01.git
https://git.dev.tencent.com/zhangguo5/javascriptpro.git