什么是闭包函数?javascript
闭包函数是一种函数的使用方式,最多见的以下:html
function fn1(){ function fn(){ } return fn; }
这种函数的嵌套方式就是闭包函数,这种模式的好处是可让内层函数访问到外层函数的变量,而且让函数总体不至于由于函数的执行完毕而被销毁。java
例如:程序员
function fn1(){ var a =10; function fn(){ console.log(a); // 10 } return fn; }
再好比下面的代码,随着函数的每次执行,变量的值都会进行递增1,缘由是由于外层函数的变量处于内层函数的做用域链当中,被内层函数所使用着,当js垃圾回收机制读取到这一状况后就不会进行垃圾回收。web
例如:ajax
function fn1(){ var a = 1; function fn(){ a++; console.log(a); } return fn; } // 调用函数 var x = fn1(); x(); // 2 x();//3
闭包函数在js的开发当中是很是常见的写法,例以下面这种写法,功能是实现了对数组的一些常规操做的封装,也是属于对闭包函数的一种应用。编程
let Utils = (function(){ var list = []; return { add:function(item){ if(list.indexOf(item)>-1) return; // 若是数组内元素存在,那么不在重复添加 list.push(item); }, remove:function(item){ if(list.indexOf(item) < 0) return; // 若是要删除的数组数组以内不存在,那么就返回 list.splice(list.indexOf(item),1); }, get_length:function(){ return list.length; }, get_showData:function() { return list; } } })(); Utils.add("hello,world"); Utils.add("this is test"); console.log(Utils.get_showData());// ["hello,world","this is test"]
在上面的代码中,函数嵌套函数造成了闭包函数的结构,在开发中是比较常见的写法。json
闭包的概念:数组
闭包是指有权限访问上一级父做用域的变量的函数
.浏览器
在js开发中,常常碰到当即执行函数
的写法。大致以下:
// 下面的这种写法就是当即执行函数 // 在函数内部的内容会自动执行 (function(){ var a = 10; var b = 20; console.log(a+b); // 30 })();
咱们也能够经过第二个括号内传入参数:
(function(i){ console.log(i); })(i);
这种自调用的写法本质上来说也是一个闭包函数
。
经过这种闭包函数,咱们能够有效的避免变量污染等问题,从而建立一个独立的做用域。
可是问题相对来讲也很明显,就是在这个独立的做用域当中,咱们没有办法将其中的函数或者变量让外部访问的到。因此若是咱们在外部须要
访问这个当即执行函数中的变量或者方法,咱们就须要经过第二个括号将window这个全局的变量对象传入,而且将须要外部访问的变量或者函数赋值
给window,这样作至关于将其暴露在了全局的做用域范围以内。
须要注意的是,一般状况下咱们只须要将必要的方法暴露,这样才能保证代码并不会相互产生过多的影响,从而下降耦合度。
例如:
(function (window){ var a = 10; // 私有属性 function show(){ return a++; } function sayHello(){ // 私有方法 alert("hello,world"); } window.show = show;// 将show方法暴露在外部 })(window);
须要理解的是,在不少的代码中,老是在(function(){})()的最前面加上一个
;
,目的是为了防止合并代码的时候js将代码解析成(function(){})()(function(){})()
这种状况。
由于js的特殊性,因此不少时候咱们在学习js的时候,除了js代码的语法之外,还要学习不少为了解决实际问题的方案,例以下面的这种写法就是为了
实现module
的写法。
例如:
var testModule = function(){ var name = "张三"; // 私有属性,外部没法访问 return { get_name:function(){ // 暴露在外部的方法 alert(name); }, set_name:function(new_name){ // 暴露在外部的方法 name = new_name; } } }
咱们也能够将这种写法进行升级,和当即执行函数进行适度的结合也是常见的写法:
例如:
var blogModule = (function (my) { my.name = "zhangsan"; // 添加一些功能 my.sayHello = function(){ console.log(this.name) } return my; } (blogModule || {})); console.log(blogModule.sayHello())
自调用函数其实也就是递归函数
自调用函数顾名思义,就是调用自身的函数,而当即执行函数则是当即会及执行的函数。
下面是两者的一些比较:
// 这是一个自执行的函数,函数内部执行自身,递归 function foo() { foo(); } // 这是一个自执行的匿名函数,由于没有标示名称 // 必须使用arguments.callee属性来执行本身 var foo = function () { arguments.callee(); }; // 这可能也是一个自执行的匿名函数,仅仅是foo标示名称引用它自身 // 若是你将foo改变成其它的,你将获得一个used-to-self-execute匿名函数 var foo = function () { foo(); }; // 有些人叫这个是自执行的匿名函数(即使它不是),由于它没有调用自身,它只是当即执行而已。 (function () { /* code */ } ()); // 为函数表达式添加一个标示名称,能够方便Debug // 但必定命名了,这个函数就再也不是匿名的了 (function foo() { /* code */ } ()); // 当即调用的函数表达式(IIFE)也能够自执行,不过可能不经常使用罢了 (function () { arguments.callee(); } ()); (function foo() { foo(); } ());
所谓的做用域
,指的就是变量
和函数
的可访问范围
,控制着变量和函数的可见性与生命周期,
在JavaScript中变量的做用域有全局做用域
和函数做用域
以及ES6新增长的块级做用域
。
例如,在函数外部经过var
关键字声明的变量就是全局变量,做用域的范围也就是全局做用域,而在函数内部
经过var
声明或者let
声明的就是局部变量,做用域仅限于函数内部,在{}
内部或者流程控制语句或者循环语句内部
经过let
声明的变量做用域范围则仅限于当前做用域。函数的参数在()
内部,只能在函数内部使用,做用域范围也仅限于函数。
同时window
对象的全部属性也拥有全局做用域。
例如:
// 做用域范围 var a = 10; // 全局 function fn1(a,b){ // 函数fn1内 c = 30; // 全局 var x = 30; // 函数fn1内 function fn2(){ var s = "hello"; // 函数fn2内 console.log(x); // 30 函数内部能够访问外层的变量 } } for(var i =0;i<10;i++){} // 循环体内声明的计数变量i也是一个全局 console.log(i); // 10 for(let j = 0;i<10;j++){} // let 声明的计数变量j 是一个局部 console.log(j);// 出错,访问不到
上面咱们说到了做用域,下面再来讲下执行环境(execution context)。
什么是执行环境呢?
简单点说,执行环境定义了变量或者函数有权访问的其余的数据,而且也决定了他们各自的行为。须要知道的
是,每个执行环境当中,都有着一个与之关联的变量对象(variable object),执行环境中定义的全部变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。
而全局执行环境是最外层的一个执行环境,在web浏览器中最外层的执行环境关联的对象是window
,因此咱们
能够这样说,全部的全局变量和函数 都是做为window对象的属性和方法建立的。
咱们建立的每个函数都有本身的执行环境,当执行流进行到函数的时候,函数的环境会被推入到一个函数执行栈
当中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的全部变量和函数定义随之销毁,控制权返回到以前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。
当代码在一个执行环境执行之时,会建立一个变量对象的一个做用域链(scope chain),来保证在执行环境中
,对执行环境有权访问的变量和函数的有序访问。
做用域第一个也d就是顶层对象始终是当前执行代码所在环境的变量对象(VO)。
例如:
function fn1(){}
fn1在建立的时候做用域链被添加进全局对象,全局对象中拥有全部的全局变量。
例如上面的fn1在建立的时候,所处的环境是全局环境,因此此时的this就指向window。
在函数运行过程当中标识符的解析是沿着做用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后再也不继续遍历,找不到就报错。
若是执行环境是函数,那么将其活动对象(activation object, AO)做为做用域链第一个对象,第二个对象是包含环境,下一个是包含环境上一层的包含环境...
也就是说所谓的做用域链,就是指具体的某个变量或者函数从其第一个对象(活动对象)一直到顶层执行环境。这中间的联系就是做用域链。
误解
的闭包函数谈及闭包函数的概念,常常会有人错误的将其理解为从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。
而实际来讲,由于做用域链,使得全部的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包)。
注意:这里只有一类函数除外,那就是经过Function构造器建立的函数,由于其[[Scope]]只包含全局对象。
闭包函数是js当中很是重要的概念,在诸多的地方能够应用到闭包,经过闭包,咱们能够写出不少优秀的代码,下面是一些常见的内容:
例如:
// 数组排序 [1, 2, 3].sort(function (a, b) { ... // 排序条件 }); // map方法的应用,根据函数中定义的条件将原数组映射到一个新的数组中 [1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6] // 经常使用的 forEach [1, 2, 3].forEach(function (element) { if (element % 2 != 0) { alert(element); } }); // 1, 3
例如咱们经常使用的call和apply方法,它们是两个应用函数
,也就是应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):
例如:
(function () { alert([].join.call(arguments, ';')); // 1;2;3 }).apply(this, [1, 2, 3]);
还有最常使用的写法:
var a = 10; setTimeout(function () { alert(a); // 10, after one second }, 1000);
固然,ajax的写法也就是回调函数其实本质也是闭包:
//... var x = 10; // only for example xmlHttpRequestObject.onreadystatechange = function () { // 当数据就绪的时候,才会调用; // 这里,不管是在哪一个上下文中建立 // 此时变量“x”的值已经存在了 alert(x); // 10 }; //...
固然也包括咱们上边说的封装独立做用域的写法:
例如:
var foo = {}; // 初始化 (function (object) { var x = 10; object.getX = function _getX() { return x; }; })(foo); alert(foo.getX()); // 得到闭包 "x" – 10
在工做当中,咱们老是能够听到人说将数据转换为JSON对象,或者说把JSON对象转换为字符串之类的话,下面是关于JSON的具体说明。
不少人错误的将JSON认为是JavaScript当中的对象字面量(object Literals),缘由很是简单,就是由于它们的语法是很是类似的,可是在ECMA中明确的说明了。JSON只是一种数据交互语言,只有咱们将之用在string上下文的时候它才叫JSON。
序列化与反序列化
2个程序(或服务器、语言等)须要交互通讯的时候,他们倾向于使用string字符串由于string在不少语言里解析的方式都差很少。复杂的数据结构常常须要用到,而且经过各类各样的中括号{},小括号(),叫括号<>和空格来组成,这个字符串仅仅是按照要求规范好的字符。
为此,咱们为了描述这些复杂的数据结构做为一个string字符串,制定了标准的规则和语法。JSON只是其中一种语法,它能够在string上下文里描述对象,数组,字符串,数字,布尔型和null,而后经过程序间传输,而且反序列化成所须要的格式。
常见的数据流行交互格式有YAML
、XML
、和JSON
都是经常使用的数据交互格式。
字面量
引用Mozilla Developer Center里的几句话,供你们参考:
何时会成为JSON
JSON是设计成描述数据交换格式的,他也有本身的语法,这个语法是JavaScript的一个子集。
{ "prop": "val" } 这样的声明有多是JavaScript对象字面量也有多是JSON字符串,取决于什么上下文使用它,若是是用在string上下文(用单引号或双引号引住,或者从text文件读取)的话,那它就是JSON字符串,若是是用在对象字面量上下文中,那它就是对象字面量。
例如:
// 这是JSON字符串 var foo = '{ "prop": "val" }'; // 这是对象字面量 var bar = { "prop": "val" };
并且要注意,JSON有很是严格的语法,在string上下文里{ "prop": "val" } 是个合法的JSON,但{ prop: "val" }和{ 'prop': 'val' }确实不合法的。全部属性名称和它的值都必须用双引号引住,不能使用单引号。
JS当中的JSON对象
目前,JSON对象已经成为了JS当中的一个内置对象,有两个静态的方法:JSON.parse和JSON.stringify。
JSON.parse主要要来将JSON字符串反序列化成对象,JSON.stringify用来将对象序列化成JSON字符串。老版本的浏览器不支持这个对象,但你能够经过json2.js来实现一样的功能。
例如:
// 这是JSON字符串,好比从AJAX获取字符串信息 var my_json_string = '{ "prop": "val" }'; // 将字符串反序列化成对象 var my_obj = JSON.parse( my_json_string ); alert( my_obj.prop == 'val' ); // 提示 true, 和想象的同样! // 将对象序列化成JSON字符串 var my_other_json_string = JSON.stringify( my_obj );
javascript中对象的建立有不少种方式,以下:
第二种形式是经过{}
这种写法直接来建立一个对象
还有一种是经过new Object()的形式建立构造函数。
经过对象字面量的形式建立对象
例如:
let obj = { value:10, setValue:function(new_value){ this.value = new_value; }, geyValue:function(){ return this.value; } }
经过构造函数的形式建立一个对象
例如:
function Person(name,age){ this.name = name; this.age = age; } var a = new Person("张三",30);
经过new Object()建立对象
例如:
var obj = new Object(); console.log(obj); // {}
下面来讲一下经过对象直接量建立的对象的一些使用方式:
属性和方法的增删改查:
例如:
// 建立一个对象直接量 var person = { name:"张三", age:40, sayHello:function(){ alert(this.name,"你好"); }, setName:function(name){ this.name = name; } } // 更改属性 person.name = "李四"; person['age'] = 40; // 更改方法 person.sayHello = function(){ return this.name + "你好"; } // 添加属性 person.like = "旅游"; // 添加方法 person.run = function(){ return `${this.name} 正在跑步`; } // 删除 delete person.age; // 查询和调用 console.log(person.name); console.log(person.run());
例如:
function Person(name,age){ this.name = name; // 初始化属性 this.age = age; } Person.prototype.sayHello = function(){ alert(this.name); } // 实例化 var zhangsan = new Person("张三",20); // 查看属性 console.log(zhangsan.name); //. 调用方法 zhangsan.sayHello(); // 修改属性 zhangsan.name = "张小三"; // 修改方法 zhangsan.sayHello = function(){ console.log(this.name + "你好"); } // 添加属性和调用属性相同 ... // 添加方法和调用方法相同 ... // 删除 delete zhangsan.name; console.log(zhangsan);
在js对象当中,存在两个属性getter和setter,经过这两个属性咱们能够进行属性的查询和赋值。
例如:
var obj = { value:10, get add(){ return this.value + 1; }, set changeVal(new_val){ return this.value = new_val; } } // 使用 console.log(obj.add); // 11 obj.changeVal = 20; console.log(obj.value); // 20
应用:变量监听
例如:
html代码以下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>变量的动态监听</title> </head> <body> <button id="btn" onclick="test()">点击</button> <p id="p">0</p> </body> <script src="test.js" charset="utf-8"></script> <script type="text/javascript"> function test(){ watchVal.value = ++watchVal.value; let info = document.getElementById("p"); info.innerHTML = watchVal.value; console.log(watchVal.value); } </script> </html>
js代码以下:
// 变量监听 var watchVal = { value:0, get val(){ console.log('取值:',this.value); return this.value; }, set val(vals) { this.value = vals; console.log("存储以后的值",this.value); } }
在诸多面向对象编程语言当中,封装
、继承
、多态
是必备的内容,而在js这门基于对象的编程语言身上,想要实现其诸多特性,只能
经过prototype
原型来实现。
咱们能够先来经过代码来简单的体验一下原型:
// 建立一个数组 var arr = [1,2,3,4]; // 打印这个数组 console.log(arr);
你能够在打印的结果中最后看到一个属性__proto__
。
你过你尝试着将其打印,你会发现其中包含了Array
的属性和方法。
它是什么呢?
它其实就是数据arr数组
的原型,而__proto__
表述的其实就是数组的原型链.
你须要知道的是,在js当中,__proto__
就是数据原型的表明,咱们能够经过其向咱们建立的数组中添加方法:
例如:
// 建立一个数组 var arr = [1,2,3,4]; // 打印这个数组 console.log(arr); // 向数组的原型上添加一个sayHello()方法 arr.__proto__.sayHello = function(){ console.log("hello,world!"); } arr.sayHello();
咱们在开发的过程当中,更多的是采用下面的写法:
// 建立一个数组 var arr = [1,2,3,4]; // 打印这个数组 console.log(arr); // 向数组的原型上添加一个sayHello()方法 Array.prototype.sayHello = function(){ console.log("hello,world!"); } arr.sayHello();
在上面的代码中,prototype
一样表示着原型,只不过更多的时候是被应用在构造函数的身上。Array
是数组对象的原型
,它是一个构造函数,因此能够经过这个属性来进行方法的设置。
当数据是一个构造函数的时候,prototype的使用方式还能够进行简化:
function Person(){ } Person.prototype = { sayHello:function(){ console.log(111) } } var a = new Person(); a.sayHello();
在编程领域,面向对象是一种常见的编程范式,也被成为OOP
,ECMASript支持包括结构化、面向对象、函数式、命令式等多种编程方式。
下面要说的就是面向对象编程的内容。
MDN说明:
面向对象编程是用抽象方式建立基于现实世界模型的一种编程模式。它使用先前创建的范例,包括模块化,多态和封装几种技术。今天,许多流行的编程语言(如Java,JavaScript,C#,C+ +,Python,PHP,Ruby和Objective-C)都支持面向对象编程(OOP)。 相对于「一个程序只是一些函数的集合,或简单的计算机指令列表。」的传统软件设计观念而言,面向对象编程能够看做是使用一系列对象相互协做的软件设计。 在 OOP 中,每一个对象可以接收消息,处理数据和发送消息给其余对象。每一个对象均可以被看做是一个拥有清晰角色或责任的独立小机器。 面向对象程序设计的目的是在编程中促进更好的灵活性和可维护性,在大型软件工程中广为流行。凭借其对模块化的重视,面向对象的代码开发更简单,更容易理解,相比非模块化编程方法, 它能更直接地分析, 编码和理解复杂的状况和过程。
面向对象编程术语:
Namespace 命名空间 容许开发人员在一个独特,应用相关的名字的名称下捆绑全部功能的容器。 Class 类 定义对象的特征。它是对象的属性和方法的模板定义。 Object 对象 类的一个实例。 Property 属性 对象的特征,好比颜色。 Method 方法 对象的能力,好比行走。 Constructor 构造函数 对象初始化的瞬间,被调用的方法。一般它的名字与包含它的类一致。 Inheritance 继承 一个类能够继承另外一个类的特征。 Encapsulation 封装 一种把数据和相关的方法绑定在一块儿使用的方法。 Abstraction 抽象 结合复杂的继承,方法,属性的对象可以模拟现实的模型。 Polymorphism 多态 多意为「许多」,态意为「形态」。不一样类能够定义相同的方法或属性。
JS当中的命名空间
命名空间是一个容器,它容许开发人员在一个独特的,特定于应用程序的名称下捆绑全部的功能。 在JavaScript中,命名空间只是另外一个包含方法,属性,对象的对象。
创造的JavaScript命名空间背后的想法很简单:一个全局对象被建立,全部的变量,方法和功能成为该对象的属性。使用命名空间也最大程度地减小应用程序的名称冲突的可能性。
例如:
咱们来建立一个全局变量叫作 MYAPP
// 全局命名空间 var MYAPP = MYAPP || {};
在上面的代码示例中,咱们首先检查MYAPP是否已经被定义(是否在同一文件中或在另外一文件)。若是是的话,那么使用现有的MYAPP全局对象,不然,建立一个名为MYAPP的空对象用来封装方法,函数,变量和对象。
咱们也能够建立子命名空间:
// 子命名空间 MYAPP.event = {};
下面是用于建立命名空间和添加变量,函数和方法的代码写法:
// 给普通方法和属性建立一个叫作MYAPP.commonMethod的容器 MYAPP.commonMethod = { regExForName: "", // 定义名字的正则验证 regExForPhone: "", // 定义电话的正则验证 validateName: function(name){ // 对名字name作些操做,你能够经过使用“this.regExForname” // 访问regExForName变量 }, validatePhoneNo: function(phoneNo){ // 对电话号码作操做 } } // 对象和方法一块儿申明 MYAPP.event = { addListener: function(el, type, fn) { // 代码 }, removeListener: function(el, type, fn) { // 代码 }, getEvent: function(e) { // 代码 } // 还能够添加其余的属性和方法 } //使用addListener方法的写法: MYAPP.event.addListener("yourel", "type", callback);
自定义对象
[类]
JavaScript是一种基于原型的语言,它没类的声明语句,好比C+ +或Java中用的。这有时会对习惯使用有类申明语句语言的程序员产生困扰。相反,JavaScript可用方法做类。定义一个类跟定义一个函数同样简单。在下面的例子中,咱们定义了一个新类Person。
function Person() { } // 或 var Person = function(){ }
[对象(类的实例)]
咱们使用 new obj 建立对象 obj 的新实例, 将结果(obj 类型)赋值给一个变量方便稍后调用。
在下面的示例中,咱们定义了一个名为Person的类,而后咱们建立了两个Person的实例(person1 and person2).
function Person() { } var person1 = new Person(); var person2 = new Person();
[构造器]
在实例化时构造器被调用 (也就是对象实例被建立时)。构造器是对象中的一个方法。 在JavaScript中函数就能够做为构造器使用,所以不须要特别地定义一个构造器方法,每一个声明的函数均可以在实例化后被调用执行。
构造器经常使用于给对象的属性赋值或者为调用函数作准备。 在本文的后面描述了类中方法既能够在定义时添加,也能够在使用前添加。
在下面的示例中, Person类实例化时构造器调用一个 alert函数。
function Person() { alert('Person instantiated'); } var person1 = new Person(); var person2 = new Person();
[属性(对象属性)]
属性就是 类中包含的变量;每个对象实例有若干个属性. 为了正确的继承,属性应该被定义在类的原型属性 (函数)中。
可使用 关键字 this调用类中的属性, this是对当前对象的引用。 从外部存取(读/写)其属性的语法是: InstanceName.Property; 这与C++,Java或者许多其余语言中的语法是同样的 (在类中语法 this.Property 经常使用于set和get属性值)
在下面的示例中,咱们为定义Person类定义了一个属性 firstName 并在实例化时赋初值。
function Person(firstName) { this.firstName = firstName; alert('Person instantiated'); } var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects alert('person1 is ' + person1.firstName); // alerts "person1 is Alice" alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"
[方法(对象属性)]
方法与属性很类似, 不一样的是:一个是函数,另外一个能够被定义为函数。 调用方法很像存取一个属性, 不一样的是add () 在方法名后面极可能带着参数. 为定义一个方法, 须要将一个函数赋值给类的 prototype 属性; 这个赋值给函数的名称就是用来给对象在外部调用它使用的。
在下面的示例中,咱们给Person类定义了方法 sayHello(),并调用了它.
function Person(firstName) { this.firstName = firstName; } Person.prototype.sayHello = function() { alert("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); // call the Person sayHello method. person1.sayHello(); // alerts "Hello, I'm Alice" person2.sayHello(); // alerts "Hello, I'm Bob"
在JavaScript中方法一般是一个绑定到对象中的普通函数, 这意味着方法能够在其所在context以外被调用。
function Person(firstName) { this.firstName = firstName; } Person.prototype.sayHello = function() { alert("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); var helloFunction = person1.sayHello; person1.sayHello(); // alerts "Hello, I'm Alice" person2.sayHello(); // alerts "Hello, I'm Bob" helloFunction(); // alerts "Hello, I'm undefined" (or fails // with a TypeError in strict mode) console.log(helloFunction === person1.sayHello); // logs true console.log(helloFunction === Person.prototype.sayHello); // logs true helloFunction.call(person1); // logs "Hello, I'm Alice"
如上例所示, 全部指向sayHello函数的引用 ,包括 person1, Person.prototype, 和 helloFunction 等, 均引用了相同的函数.
在调用函数的过程当中,this的值取决于咱们怎么样调用函数. 在一般状况下,咱们经过一个表达式person1.sayHello()来调用函数:即从一个对象的属性中获得所调用的函数。此时this被设置为咱们取得函数的对象(即person1)。这就是为何person1.sayHello() 使用了姓名“Alice”而person2.sayHello()使用了姓名“bob”的缘由。
然而咱们使用不一样的调用方法时, this的值也就不一样了。当从变量 helloFunction()中调用的时候, this就被设置成了全局对象 (在浏览器中即window)。因为该对象 (很是可能地) 没有firstName 属性, 咱们获得的结果即是"Hello, I'm undefined". (这是松散模式下的结果, 在 严格模式中,结果将不一样(此时会产生一个error)。 可是为了不混淆,咱们在这里不涉及细节) 。
call和apply能够显示的更改this
继承
建立一个或多个类的专门版本类方式称为继承(Javascript只支持单继承)。 建立的专门版本的类一般叫作子类,另外的类一般叫作父类。 在Javascript中,继承经过赋予子类一个父类的实例并专门化子类来实现。在现代浏览器中你可使用 Object.create 实现继承.
在下面的例子中, 咱们定义了 Student类做为 Person类的子类. 以后咱们重定义了sayHello() 方法并添加了 sayGoodBye() 方法.
// 定义Person构造器 function Person(firstName) { this.firstName = firstName; } // 在Person.prototype中加入方法 Person.prototype.walk = function(){ alert("I am walking!"); }; Person.prototype.sayHello = function(){ alert("Hello, I'm " + this.firstName); }; // 定义Student构造器 function Student(firstName, subject) { // 调用父类构造器, 确保(使用Function#call)"this" 在调用过程当中设置正确 Person.call(this, firstName); // 初始化Student类特有属性 this.subject = subject; }; // 创建一个由Person.prototype继承而来的Student.prototype对象. // 注意: 常见的错误是使用 "new Person()"来创建Student.prototype. // 这样作的错误之处有不少, 最重要的一点是咱们在实例化时 // 不能赋予Person类任何的FirstName参数 // 调用Person的正确位置以下,咱们从Student中来调用它 Student.prototype = Object.create(Person.prototype); // See note below // 设置"constructor" 属性指向Student Student.prototype.constructor = Student; // 更换"sayHello" 方法 Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); }; // 加入"sayGoodBye" 方法 Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); }; // 测试实例: var student1 = new Student("Janet", "Applied Physics"); student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student1.walk(); // "I am walking!" student1.sayGoodBye(); // "Goodbye!" // Check that instanceof works correctly console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true
对于“Student.prototype = Object.create(Person.prototype);”这一行,在不支持 Object.create方法的老JavaScript引擎中,
可使用相似下面的代码来进行解决:
function createObject(proto) { function ctor() { } ctor.prototype = proto; return new ctor(); } // Usage: Student.prototype = createObject(Person.prototype);