函数
定义函数的三种方式
function 语句形式
window.onload = function () {
sayHello();
};
function sayHello(){
console.log('Hello World');
}
函数的直接量
window.onload = function () {
sayHello();
};
var sayHello = function () {
console.log('Hello World');
}
Function 函数构造函数
window.onload = function () {
console.log(sayHello(1,2));
};
var sayHello = new Function("a","b","return a + b ");
区别
推荐使用 function语句或函数直接量形式
arguments 对象
arguments
是函数的内部的一个数组对象,保存了函数的参数列表,arguments
还有一个属性叫做:callee
,该属性是一个指针,指向拥有这个arguments
对象的函数
一个使用arguments
的经典例子:
window.onload = function () {
console.log(factorial(5));
};
var factorial = function ( num ) {
if( num <= 1 ){
return 1;
}else{
return num * arguments.callee(num-1);
}
}
this 对象
this对象是在运行时基于函数的执行环境绑定的.在全局函数中this指向window,当函数被作为某个对象的方法调用时,this指向那个对象。
也就是说this总是指向调用者
length
函数的length
属性表示函数希望接收的命名参数的个数
call 和 apply
每个函数都包含两个非继承而来的方法:call
和apply
cal
和apply
的第一个参数都是this
,指向运行函数的作用域,apply
的第二个参数可以是arguments
,也可以是数组,call
的其它参数必须完整的列出.
简单用法
- 传递参数
window.onload = function () {
console.log(fTwo(1,2));
};
var fOen = function (a,b) {
return a + b;
}
var fTwo = function (a,b) {
return fOen.call(this,a,b);
}
高级用法
扩充函数赖以运行的作用域
window.authorName = 'laolang';
var obj = {authorName:'xiaodaima'};
function showAuthorName( ){
console.log(this.authorName);
}
showAuthorName.call(window);
showAuthorName.call(obj);
函数作参数
var sum = function (a,b) {
return a + b;
}
var minus = function (a,b) {
return a - b;
}
var doSomething = function (calFunName,a,b) {
return calFunName(a,b);
}
console.log(doSomething(sum,1,2));
console.log(doSomething(minus,1,2));
执行环境和作用域链
执行环境(execution context)是javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台执行它。
全局执行环境是最外围的一个执行环境。根据ECMScript实现所在的宿主环境不同,表示执行环境的对象也不一样。 每一个函数都有自己的执行环境。当执行流进一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返还给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
var color1 = "blue";
function changeColor(){
var color2 = "red";
function swapColor(){
var color3 = color2;
color2 = color1;
color1 = color3;
//这里可以访问:color1、2、3
}
//这里可以访问color1、color2、但不能访问color3
swapColor();
}
//这里只能访问color1
changeColor();
闭包
三种概念
- 闭包与函数有着紧密的关系,它是函数的代码在运行过程中的一个动态环境,是一个运行期的、动态的概念。
- 所谓闭包,是指词法表示包括不必计算的变量的函数。也就是说,该函数能够使用函数外定义的变量.。
- 在程序语言中,所谓闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。这些外部执行域的非持久型变量神奇地保留他们在闭包最初定义(或创建)时的值。
两段JS代码
var name = "xiaodaima";
var obj={
name : "laolang",
getName : function () {
return function () {
return this.name;
}
}
}
console.log(obj.getName()());
这段代码返回的是
xiaodaima
这是因为obj.getName()
返回的函数是在window
域中,所以返回值是外部定义的全局变量xiaodaima
var name = "xiaodaima";
var obj={
name : "laolang",
getName : function () {
var o = this;
return function () {
return o.name;
}
}
}
console.log(obj.getName()());
这段代码返回的是
laolang
而这段代码因为将obj
的引用o
想用到的返回的函数中,所以返回的函数作用域在obj
对象中,所以返回的是函数内部的局部变量laolang
闭包小例子
闭包特性:一个函数可以访问另一个函数作用域中的变量,直到一个保护变量的作用
对象
字面量形式
var person = new Object();
person.name = "laolang";
person.age= 23;
person.job = "Software Engineer";
person.sayName = function () {
console.log(this.name);
}
var person ={
name : 'laolang',
age : 23,
job : 'Software Engineer',
sayName : function () {
console.log(this.name);
}
}
这种形式有个明显的缺点,:使用同一接口创建很多对象时,会产生大量的重复代码,于是就有了各种创建对象的替代方法.
工厂模式
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
};
return o;
}
var p1 = createPerson('laolang',23,'Software Engineer');
p1.sayName();
var p2 = createPerson('xiaodaima',32,'Doctor');
p2.sayName();
这种方式虽然解决了创建多个相似对象的问题,但是没有解决对象识别的问题,即怎样知道一个对象的类型
构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
};
}
var p1 = new Person('laolang',23,'Software Engineer');
p1.sayName();
var p2 = new Person('xiaodaima',32,'Doctor');
p2.sayName();
console.log(p1.constructor == Person ); // true
console.log(p2.constructor == Person ); // true
console.log(p1 instanceof Person); // true
console.log(p1 instanceof Object); // true
console.log(p2 instanceof Person); // true
console.log(p2 instanceof Object); // true
使用这种方式创建对象必须使用new
操作符,会经历以下4个步骤:
-
创建一个对象
-
将构造函数的作用域赋给新对象【因此this就指向了这个新对象】
-
执行构造函数中的代码【为这个新对象添加属性】
-
返回对象
实际上任何函数都可以用new
操作符来调用,任何函数如果不用new
操作符来调用,那就是普通函数,用了new
操作符就是构造函数.
而使用这种方式在不使用new
操作符的时候,this
就是指向了window
对象,这是因为不用new的时候调用这个函数的就是window
对象,如下方式就会使得对象o
有了Person
所有的属性和方法
var o = new Object();
Person.call(o,'laolang',23,'Software Engineer');
o.sayName();
console.log(o);
这是因为此时函数是在对象o
的作用域中调用的。
但是由于JS中的函数都是对象,所以每次实例化的时候,每个类的方法都重新创建了一次,也就是说每个实例的同名方法都是不同的,这会导致不同的作用域链和标识符解析
console.log(p1.sayName == p2.sayName ); // false
为了解决这个问题可以使用将类的方法的定义移到外面
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
var p1 = new Person('laolang',23,'Software Engineer');
p1.sayName();
var p2 = new Person('xiaodaima',32,'Doctor');
p2.sayName();
console.log(p1.sayName == p2.sayName ); // true
但是这种方式几乎没有封装性可言,而且如果需要定义很多方法的话,就要定义很多个全局函数
原型模式
function Person(){}
Person.prototype.name = 'laolang';
Person.prototype.age = 23;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function () {
console.log(this.name);
}
var p1 = new Person();
p1.sayName();
console.log(p1);
var p2 = new Person();
p2.sayName();
console.log(p2);
console.log(p1.sayName == p2.sayName ); // true
console.log(p1.name == p2.name ); // true
这种方式虽然解决了实例化类中的方法是相同引用,但是也造成了一个问题,那就是如果使用构造函数之后,不修改实例的属性值 ,那么每个实例化的对象的所有属性也是相同的引用 。原因就是每个函数的
prototype
指向的是函数的原型对象。
关于原型对象我没有看明白,书上一个简洁明了的解释如下 :
当我们调用
p1.sayName()
时,解析器会问:实例p1中有sayName属性吗?答:没有。再问:p1的原型中有sayName属性吗?答:有。于是它就读取那个保存在原型对象中的函数。
于是这也就造成了所有实例化的类属性和方法都是相同的引用。而当我们对实例的属性赋值时,实例就具有了属性,这就把原型中的属性给屏蔽了,其实也就是实例中的属性的引用不再指向原型了,但是可以用delete
将实例中的属性删除,这样又可以访问原型中的属性了。
function Person(){}
Person.prototype.name = 'laolang';
Person.prototype.age = 23;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function () {
console.log(this.name);
}
var p1 = new Person();
var p2 = new Person();
p1.name = 'xiaodaima';
console.log(p1.sayName == p2.sayName ); // true
console.log(p1.name == p2.name ); // false
console.log(p1.name == p2.name ); // true
而这也是原型模式最大的缺点,即所有属性都是被很多实例共享的
组合使用构造函数模式和原型模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor : Person,
sayName : function (){
console.log(this.name);
}
}
var p1 = new Person('laolang',23,'Software Engineer');
var p2 = new Person('xiaodaima',34,'Doctor');
console.log(p1.sayName == p2.sayName ); // true
console.log(p1.name == p2.name ); // fasle
这样把属性定义在构造函数中,把方法定义在构造函数的原型中,就可以解决问题,这种方式也是使用最广泛的方式
动态原型模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
if ( typeof this.sayName != "function" ){
Person.prototype.sayName = function () {
console.log(this.name);
}
}
}
var p1 = new Person('laolang',23,'Software Engineer');
var p2 = new Person('xiaodaima',34,'Doctor');
console.log(p1.sayName == p2.sayName ); // true
console.log(p1.name == p2.name ); // fasle
使用这种方式时不能使用对象字面量生写原型,会切断现有实例与新原型之间的联系
寄生构造函数模式
稳妥构造函数模式
继承
许多OO语言都支持两种继承:接口继承和实现继承。接口继承只继承方法签名,而 实现继承则继承实际的方法。由于JS函数没有签名,所以JS只支持实现继承,而其实现继承主要是依靠原型链来实现的
原型链
其基本思想是利用原型主一个引用类型继承另一个引用类型的属性和方法.
function Persion(){
this.name = 'laolang';
this.age = 23;
}
Persion.prototype.sayHello = function () {
console.log(this.name);
};
var p = new Persion();
p.sayHello();
console.log(p);
function Student(){
this.name = 'stu';
}
Student.prototype = new Persion();
Student.prototype.sayInfo = function () {
console.log(this.name);
};
var stu = new Student();
stu.sayInfo();
console.log(stu);
使用这种方式实现继承需要注意:
-
子类定义方法需要在
Student.prototype = new Persion();
语句之后 -
不能使用字面量的形式添加新方法
Student.prototype = {
sayInfo : function () {
console.log(this.name);
}
};
这样会报一个找不到方法的错误,原因是把子类的原型指向父类实例之后,紧接着又把子类的原型替换成一个字面量
原型链继承的问题:
-
所有的子类实例都共享了所有的引用类型属性[比如数组]
-
创建子类实例时,不能向父类的构造函数中传递参数
借用构造函数
基本思想是在子类构造函数的内部调用父类构造函数
function Persion(name){
this.name = name;
}
function Student(){
Persion.call(this,'stu');
this.age = 2;
}
var stu = new Student();
console.log(stu);
借用构造函数的问题:
方法都在构造函数中定义,因此函数复用无从谈起,且父类的方法对子类也是不可见的
组合继承
基本思想是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承.这样在原型上定义方法实现了函数利用,又能保证每个实例都有自己的属性
function Persion ( name ){
this.name = name;
this.colors = ['red','blue','green'];
}
Persion.prototype.sayName = function () {
console.log(this.name);
};
function Student(name,age){
// 继承属性
Persion.call(this,name);
this.age = age;
}
// 继承方法
Student.prototype = new Persion();
Student.prototype.sayAge = function () {
console.log(this.age);
};
var stu1 = new Student('stu1',21);
stu1.colors.push('black');
stu1.sayAge();
stu1.sayName();
console.log(stu1);
var stu2 = new Student('stu2',22);
stu1.sayAge();
stu1.sayName();
console.log(stu2);
这样每个类可以有自己的属性而又共享方法
组合继承也是JS中最常用的继承模式,且instanceof
和isPrototypeOf()
也能用于识别基于组合继承创建的对象
原型式继承
寄生式继承
寄生组合式继承
接口
建立JS接口的三种方式:
-
注释描述接口
-
属性检测接口
-
鸭式辩型接口
接口的利弊:
-
对于一些中小型程序来说 使用接口很显然是不明智的,对项目来说接口的好处也不明显,只是徒增其复杂度而已。
-
对于接口的好处,那么显而易见 首先促进代码的重用,对于开发来讲,还可以告诉程序员那些类都使用了什么方法,如果你事先知道接口那么就减少了你在编码的时候对类与类之间冲突,实现解耦。对于测试和调试也会变得轻松,用于javascript的弱类型语言,类型不匹配经常出现,那么使用接口,这一点会变得容易一些。
注释描述接口
/**
* interface Composite{
* function add(obj);
* function remove(obj);
* function update(obj);
* }
*/
// CompositeImpl implements Composite
function CompositeImpl(){
}
CompositeImpl.prototype.add = function (obj) {
console.log('add...');
};
CompositeImpl.prototype.remove = function (obj) {
console.log('remove...');
};
CompositeImpl.prototype.update = function (obj) {
console.log('update...');
};
- 优点:
程序员可以有一个参考
- 缺点:
太松散,无法检查接口是否实现
属性检测
/**
* interface Composite{
* function add(obj);
* function remove(obj);
* function update(obj);
* }
* interface FormItem{
* function select(obj);
* }
*/
// CompositeImpl implements Composite , FormItem
var CompositeImpl = function(){
// 显示的在类的内部,接收所实现的接口
// 是一个规范:在类的内部定义一个数组,名字要固定
this.implementsInterfaces = ['Composite','FormItem'];
// this.implementsInterfaces = ['Composite'];
}
CompositeImpl.prototype.add = function (obj) {
console.log('add...');
};
CompositeImpl.prototype.remove = function (obj) {
console.log('remove...');
};
CompositeImpl.prototype.update = function (obj) {
console.log('update...');
};
CompositeImpl.prototype.select = function (obj) {
console.log('select...');
};
function CheckCompositeImpl(instance){
// 判断当前对象是否实现了所有接口
if( !IsImplements(instance,'Composite','FormItem')){
throw new Error('Object does not implements a require interface');
}
}
// 公用的具体的检测方法,返回 boolean
function IsImplements(obj){
// 从接口名字开始
for( var i = 1; i < arguments.length; i++ ){
// 接收接口的名字
var interfaceName = arguments[i];
// 判断此方法是成功还是失败
var interfaceFound = false;
for ( var j = 0; j < obj.implementsInterfaces.length; j++ ){
if( interfaceName == obj.implementsInterfaces[j]){
interfaceFound = true;
break;
}
}
if( !interfaceFound ){
return false;
}
}
return true;
}
var c1 = new CompositeImpl();
CheckCompositeImpl(c1);
c1.add(1);
鸭式辨型法
/**
* 接口类
* @param {string} name 接口名
* @param {string[]} methods 接收方法名称的集合[数组]
*/
var Interface = function (name, methods) {
if( arguments.length != 2 ) {
throw new Error('This instance interface constructor must be 2 length!');
}
this.name = name;
this.methods = [];// 内置的空数组对象,用来接收参数methods里的元素
for ( var i = 0 ; i < methods.length; i++ ){
if( !typeof methods[i] === 'string' ){
throw new Error('The name of interface method\'s type must be string!');
}
this.methods.push(methods[i]);
}
};
// 接口实例
var CompositeInterface = new Interface('CompositeInterface',['add','remove']);
var FormItemInterface = new Interface('FormItemInterface',['update','select']);
// 实现类
var CompositeImpl = function(){};
CompositeImpl.prototype.add = function (obj) {
console.log('add...');
};
CompositeImpl.prototype.remove = function (obj) {
console.log('remove...');
};
CompositeImpl.prototype.update = function (obj) {
console.log('update...');
};
CompositeImpl.prototype.select = function (obj) {
console.log('select...');
};
// 检测方法
Interface.ensueImplements = function (obj) {
// 如果参数长度小于2则失败
if( arguments.length < 2 ){
throw new Error('Interface.ensueImplements method constructor arguments\'s length must be >= 2');
}
for( var i = 1; i < arguments.length; i++ ){
var instanceInterface = arguments[i];
// 判断参数是否是接口类的类型
if( instanceInterface.constructor !== Interface ){
throw new Error('The arguments constructor not be Inrerface Class');
}
for( var j = 0; j < instanceInterface.methods.length; j++ ){
// 方法名
var methodName = instanceInterface.methods[j];
if( !obj[methodName] || typeof obj[methodName] !== 'function'){
throw new Error('The method:' + methodName + ' is not found');
}
}
}
};
// 实现类实例
var c1 = new CompositeImpl();
// 检测是否实现接口
Interface.ensueImplements(c1,CompositeInterface,FormItemInterface);
c1.add(1);
完美的接口实现