函数
- 函数包含一组语句,是JavaScript的基础模块单元,用于
- 代码复用
- 信息隐藏
- 组合调用
- 指定对象的行为
- 所谓编程,就是将一组需求分解成一组函数与数据结构的技能
函数对象
- 函数就是对象
- 对象是名值对的集合并拥有一个连到原型对象的隐藏连接
- 对象字面量产生的对象连接到Object.prototype
- 函数对象连接到Function.prototype(其本身也是连接到Object.prototype)
- 每个函数创建时会附加两个隐藏属性
- 函数上下文
- 实现函数行为的代码
- 注意和隐藏连接到Function.prototype的区别.什么区别呢我也不知道
var test = function test (){};
console.log(test.prototype)
-
函数是对象,所以它能像对象一样使用可以保存在
- 变量
- 对象
- 数组中.
-
可以当做参数传递给其他函数,也可以再返回函数.
-
因为函数是对象,所以函数也可以拥有方法
-
函数的与众不同,在于他们可以被调用
函数字面量
- 4个部分
- 保留字function+函数名(可省略)
- 包括在圆括号中的一级参数(逗号隔开,调用时才被初始化为实际提供的参数,实参)
- 包括在花括号中的一经且语句
var add = function (a,b) {
return a+b;
};
- 函数字面量可以出现在任何允许表达式出现的地方.
- 可以被定义在其他函数中
- 一个内部函数除了可以访问自己的参数和变量,也能自由访问其父函数中的参数与变量
- 通过函数字面量创建的函数对象包含一个连到外部上下文的连接,被称为闭包....就是指上面这一行话.内部可以访问外部(父级)
调用
-
调用函数会暂停当前函数执行,传递控制权和参数给新函数.
-
除了声明时定义的形参,每个函数还接收两个附加的参数
- this
- arguments.
-
this取决于调用模式的不同而值不同
- 方法调用模式
- 函数调用模式
- 构造器调用模式
- apply调用模式
-
调用运算符跟在任何产生一个函数值的表达式之后的一对圆括号.
-
圆括号内可以有0或多个用逗号分开的表达式,每个表达式产生一个参数值,对应到形参上
-
实参的个数比形参多,则忽略多的,如果少,则以undefined填充
-
方法调用模式
- 当函数被保存为对象的一个属性时,称之为一个方法
- 当方法被调用时,this被绑定到该对象上
- 如果有提取动作.,那么就是方法调用
- 方法可以使用this访问自己所属的对象,所以能从对象中取值或进行修改
- this的绑定发生在调用的时候,这种延迟绑定可以对this实现高度复用
- 通过this属性取得它们对象所属的上下文的方法叫公共方法
var myObject = {
value:0,
increment:function(inc){
this.value += typeof(inc) === 'number' ? inc : 1;
}
};
myObject.increment();//方法调用模式
document.writeln(myObject.value);//=>1
myObject.increment(2);
document.writeln(myObject.value);//=>3
- 函数调用模式
- 当一个函数并非一个对象的属性时,那么它就是被当作一个函数来调用的
- 函数模式调用时,this被绑定到全局对象即window
var sum = add(3,4); // 函数调用模式
// 通过that来避免被绑定到全局变量上
myObject.double = function(){
var helper = function(){
this.value = add(this.value,this.value);
};
//因为helper是函数调用模式,所以这里的this,并不是访问的myObject里面的this.而是访问的window的this.
helper();
};
myObject.double();
console.log(myObject.value); // => 3
myObject.double = function(){
// 因为double是方法调用,所以这个this是myObject
var that = this;
helper = function(){
that.value = add(that.value,that.value);
};
helper();
};
myObject.double();
console.log(myObject.value); // => 6
- 构造器调用模式
- javascript是基于原型继承的语言,直接从对象进行继承.所以是没有类型这一说的.
- 主流语言都是基于类,所以javascript也引用了基于类的类似的对象构建语法
- 在函数前面加上New来调用,则会创建一个连接到该函数的prototype成员的新对象.this就绑定到这个新对象上(就是说复制了一个函数,也不叫复制,因为新函数对象就只拥有旧函数的原型Prototype里面的东西,而旧函数里面的属性和方法都不会继承下来)
- new 前缀也会改变return语句的行为....不懂
var Quo = function (string) {
this.status = string;
};
Quo.prototype.get_status = function(){
return this.status;
};
//因为是链接到Quo原型的,所以拥有Quo原型上属性和方法
var myQuo = new Quo("confused");
console.log(myQuo.get_status(),myQuo.status); // => confused confused
// 因为是函数调用,所以this是指向window.status是给window添加了一个属性,这里要返回undefined....
// 因为是给prototype添加了一个get方法,所以直接调用get_status()没用.
// 因为他本身没有这个方法,即使是调用prototype.get_status()也返回undefined,因为方法里面的this还是指向了本身
Quo("xxx");
console.log(Quo.get_status(),Quo.status); // => undefined,undefined
-
如果直接在Quo里面声明一个变量和方法,那Quo可以直接调用..这不必多说,
-
但是new出来的myQuo就没有这个变量和方法了,因为myQuo是链接到他的prototype上的
-
不推荐使用构造器new,,即使要用也要大写函数名
-
Apply调用模式
- 因为javascript是函数式编程语言,所以函数可以拥有方法(c#的方法里面就不能再用方法了)
- apply可以构建一个参数数组传递给函数,并且可以自定义this的值.
// add函数默认只接受两个参数,多的会被忽略掉
var array = [3,4,5];
//第一个参数是改变调用函数对象的this值,第二个是参数数组
var sum = add.apply(null,array);
console.log(sum); // => 7
var stutusObject = {
status:'A-OK'
};
//相当于给stutusObject临时加上了一个get_status方法
console.log(myQuo.get_status.apply(stutusObject,[])); // => A-OK
参数
- 函数调用时,有一个隐藏参数arguments数组.包含所有实参.
var sum = function() {
var i, sum = 0;
for (i = 0; i < arguments.length; i += 1) {
sum += arguments[i];
};
return sum;
};
console.log(sum(1,2,3,4)); // => 10
- 要注意的是,arguments并不是一个真正的数组
- 只是类似数组的对象
- 只有length属性没有数组的方法
返回
- 当函数被调用时,它从第一个语句开始执行,并遇到关闭函数体的}结束,然后把控制权交给调用该函数的程序
- return可提前返回函数,后面的语句不再执行
- 函数总要返回值,如果没有指定,则返回undefined
- 如果是构造器调用模式,返回值如果不是一个对象,那么就返回this
var te2 = new te();
console.log(te2) //=> te{}
var te = function(){
var x = 1;
return {};
}
var te3 = new te();
console.log(te3)// => {}
异常
- throw中断函数执行
- 招聘一个exception对象
- 至少包含name和message属性
- ....然后跳到catch里面去
var add = function(a,b){
if(typeof a !== 'number' || typeof b !== 'number'){
throw {
name:'TypeError',
message:'add needs numbers'
};
};
return a + b;
};
try {
add('',3);
} catch(e) {
console.log(e);
}
扩充类型的功能
- 给语言的基本类型扩充功能,类似于c#的扩展方法
// 给所有函数添加一个method方法
Function.prototype.method = function(name,func){
this.prototype[name] = func;
return this;
};
// 给Number函数添加一个integer方法
Number.method('integer',function(){
return Math[this<0?'ceil':'floor'](this);
});
console.log((-10/3).integer(),(10/3).integer());//-3,3
String.method('trim',function(){
return this.replace(/^\s+|\s+$/,'')
});
console.log(' neat '.trim());
- 因为基本类型的原型是公有结构.尽量还是不要乱加方法进去
递归
- 这个需要专门的练习
- 会直接或间接调用自身的一种函数
- 汉诺塔算法,代码能够看懂,但是脑子里面逻辑走不通
var hanoi = function(disc,src,aux,dst){
if(disc>0){
hanoi(disc - 1,src,dst,aux);
console.log('move disc '+disc+'from '+src +' to ' + dst);
hanoi(disc - 1,aux,src,dst);
}
};
hanoi(2,'左边','中间','右边');
- 递归可以很容易的正理树形结构 ,比如浏览器的文档对象模型DOM
- 依次访问HTML中的每个子节点...下面是查找某个属性值为xxx的节点列表
var walk_the_DOM = function walk (node,func){
func(node);// 在当前节点上执行方法
node = node.firstChild;//获取第一个子节点
while(node){ // 如果有子节点或弟弟节点
walk(node,func);//就递归..如果子节点或弟弟节点没有子节点,就中断函数
node = node.nextSibling; //就找弟弟节点
}
};
var getElementsByAttribute = function(attr,value){
var results =[];
walk_the_DOM(document.body,function(node){
var actual = node.nodeType ===1 && node.getAttribute(attr);//如果是元素节点就查找属性
if(typeof actual === 'string' && (actual === value || typeof value !== 'string')){//如果有属性值且属性值和参数一样或参数值不是字符串,就保存这个节点
results.push(node);
}
});
return results;
};
console.log(getElementsByAttribute('id','a'));
尾递归
- 就是那个什么拉..不是拉..是裴波那契数列....因为函数返回自身递归调用 的结果..就是说子函数的结果父函数要拿去用的..可以用一种循环的方式去优化...
- javascript没有进行这样的优化
var factorial = function(i,a){
a = a||1;
if(i<2){
return a;
}
return factorial(i-1,a*i);
}
console.log(factorial(4,'xx'));
作用域
- 作用域控制着参数和变量的可见性及生命周期.
- 减少了名称冲突并且提供了自动的内存管理
var foo =function(){
var a=3,b=5;
var bar = function(){
var b=7,c=11;
a +=b+c;
};
bar();
};
- javascript只有函数作用域,但是没有块级作用域,即if里面声明的东西,在其外面也可以访问
- 延迟声明变量在javascript中行不通...因为没有块级作用域,即使延迟声明和前置声明也是一样的效果.所以在函数顶部声明所有变量
闭包
- 作用域的好处是内部函数可以访问父函数的参数和变量,除了this和auguments之外..
- 内部函数比外部函数拥有更长的生命周期.
var myObject = (function(){ //是将return后面那个对象赋值给myObject
var value = 0;
return {
increment:function(inc){
value +=typeof(inc) === 'number'?inc:1;
},
getValue:function(){
return value;
}
};
}()); // 这对括号是直接调用这个匿名函数
console.log(myObject.getValue());//虽然myObject里面没有value属性,但是也能够访问它,因为是子函数
和上面这个例子差不多的另外一个更明显的例子..当父函数返回后,子函数还是能够调用父函数的参数和变量,就叫闭包
var quo = function(status) {
return {
get_status: function() {
return status;
}
};
};
var myQuo = quo('xxx');//函数执行完之后就返回了
console.log(myQuo.get_status()); //虽然quo已经返回了,但是myQuo里面的方法还是能够return status.因为作用域的关系,这就叫闭包
这是一个可见的案例
var fade = function(node){
ar level = 1;
var step = function(){
var hex =level.toString(16);
console.log(hex);
node.style.backgroundColor ="#FFFF" + hex +hex;
if(level<15){
level +=1;
setTimeout(step,300);
}
};
step(step,100);
};
fade(document.getElementById("a"));
一个经常出错的用法
var add_the_handlers = function(nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
//Nodes后面的i和var i是在一个作用域里面的.所以值一直在变
nodes[i].onclick = function(e) {
console.log(i);//这里只是声明了一个函数,i一直只是一个引用,值根本不会变.只有在执行的时候才会去调用i变量.那个时候i已经是Length了
}
};
};
var add_the_handlers = function(nodes){
var i;
//整个alertIndex就是一个闭包,他返回一个其名函数,接收一个e的参数..但是他的代码块,则是返回index这个参数.而这个参数,是每次循环的时候就固定赋值的
var alertIndex = function(index){
return function(e){
console.log(index);
};
};
for(i=0;i<nodes.length;i+=1){
nodes[i].onclick = alertIndex(i);//这里是直接调用方法,...立即就执行了这个方法,一共扫行了length次,,就不会出现上面那种情况了
}
};
在循环内部不要创建函数.无谓的计算
回调
- 同步执行网络请求会让浏览器限入假死状态.因为请求没回来的时候一直要卡UI线程
- 发送请求request
- 接收回应response......这个过程就会一直卡死浏览器
- 渲染浏览器
- 所以要发起异步请求,当服务器响应到达时,在异步里面立即触发回调函数,就不会阻塞客户端
- 异步发起请求asyn request
- 在异步方法里面接收回应 resonse...这样就不会卡死浏览器
- 渲染浏览器
- 怎么自定义一个回调?????..其实就是把函数做为参数传递给另一个函数就是回调..因为这样一传,函数就成了一个参数..就会延迟到他被调用的时候进行执行
模块
- 模块是一个提供接口却隐藏状态与实现的函数或对象..
- 可以使用函数和闭包来构造模块
- 通过函数产生模块,可以完全摒弃全局变量的使用
- 比如我们要把用户提交的博客内容中的html代码删除掉,则要对他的内容,按照html标签规则进行过滤..那么这个html标签规则是存在哪里呢?
- 正常情况下是这样的
var entity ={
quot:'"',
lt:'<',
gt:'>'
}
xx.onclick = function(){
this.value.replace(/&([^&;])/g,'entity相关的规则')
};
// 这样就多出来一个entity全局变量....不推荐的说
// 但是如果写在函数里面也有问题.虽然没有了全局变量.但因为每加载一次方法,就会计算一下整个entity变量
xx.onclick = function(){
var entity ={
quot:'"',
lt:'<',
gt:'>'
}
this.value.replace(/&([^&;])/g,'entity相关的规则')};
// 正确的应该是下面这种做法 String.method('deentityify',function(){ var entity ={ quot:'"', lt:'<', gt:'>' }; // 下面这个函数才是赋值给deentityify的函数,因为当前函数后面跟了一对小括号,所以是立即执行了一下.返回了下面这个函数 return function(){ return this.replace(/&([^&;]+);/g,function(a,b){ var r = entity[b]; return typeof r === 'string' ?r:a; }); }; }()); console.log("<">".deentityify());
- 定义了私有变量和函数的函数,利用闭包创建可以访问私有变量和函数的特权函数,最后返回这个特权函数......有利于减少全局变量,封闭应用程序和构造单例对象...好吧.有点小复杂....明白是明白了.运用在哪里运用呢.以后再说吧
- 也可以用来生产安全的对象,就是让外部不能访问私有变量.只能通过开放的方法来访问它..
## 级联
- 函数返回this.就是级联....就像Jquery中的修改dom属性一样.$('x').css().class().....
## 柯里化
- 就是让函数和他的参数构成一个新的函数
## 记忆
- 记住之前的操作记录,避免重复计算...这应该算是一种编程思想吧.....
所有的回调,模块,级联,柯里化和记忆都是建立在闭包基础上的.而闭包又是建立的作用域基础上的.....