JavaScript 函数用法详解【函数定义、参数、绑定、作用域、闭包等】
本文实例讲述了JavaScript 函数用法。分享给大家供大家参考,具体如下:
初始函数
Function类型,即函数的类型。
典型的JavaScript函数定义:
function 函数名称(参数表){ //函数执行部分 return ; } //注意:参数列表直接写形参名即可
return语句:return返回函数的返回值并结束函数运行
函数也可以看做数据来进行传递
参数列表相当于函数入口,return 语句相当于函数出口
函数可以作为参数来传递。
function test ( a ) { a(); } test(function () { alert(1); });
函数可以嵌套定义
function test2(){ function test3(){ alert(1); } test3(); } test2();
定义函数
三种定义函数的方式:
function语句形式
函数直接量形式
通过Function构造函数形式定义函数
//1 function 语句式 function test1 () { alert(1); } //2 函数直接量 (ECMAScript 推荐) var test2 = function () { alert(2); } //3 function 构造函数式 var test3 = new Function('a','b','return a+b;'); //最后一个参数是函数结构体 test3(10,20);
function语句 | Function构造函数 | 函数直接量 | |
---|---|---|---|
兼容 | 完全 | js1.1以上 | js1.2以上版本 |
形式 | 句子 | 表达式 | 表达式 |
名称 | 有名 | 匿名 | 匿名 |
性质 | 静态 | 动态 | 静态 |
解析时机 | 优先解析 | 顺序解析 | 顺序解析 |
作用域 | 具有函数的作用域 | 顶级函数(顶级作用域) | 具有函数作用域 |
静态动态的区别
var d1 = new Date(); var t1 = d1.getTime(); for ( var i=0; i<10000000; i++ ) { // function test1 () {} // 342ms //在函数体只会被声明一次 ,其它地方并不再解析,就可以调用 。 //静态 //只会编译一次,放入内存中。 var test2 = function () {} //354ms // var test3 = new Function(); //8400ms //每次执行,都是重新new一个 函数。 //new 完之后, 就销毁了。不占用内存。动态创建一次。 } var d2 = new Date(); var t2 = d2.getTime(); console.log( t2 - d1 );
解析顺序
function f () { return 1; } // 函数1 alert( f() ); //返回值为4 说明第1个函数被第4个函数覆盖 var f = new Function("return 2;"); // 函数2 alert( f() ); //返回值为2 说明第4个函数被第2个函数覆盖 var f = function () { return 3; } // 函数3 alert( f() ); //返回值为3 说明第2个函数被第3个函数覆盖 function f () { return 4; } // 函数4 alert(f()); //返回值为3 说明第4个函数被第3个函数覆盖 var f = new Function("return 5"); // 函数5 alert(f()); //返回值为5 说明第3个函数被第5个函数覆盖 var f = function(){ return 6; } // 函数6 alert(f()); //返回值为6 说明第5个函数被第6个函数覆盖
函数作用域
var k = 1 ; function t1(){ var k = 2 ; // function test(){return k ;} //2 // var test = function(){ return k}; //2 var test = new Function('return k;'); //1 //new Function(); 是会有顶级作用域 alert(test()); } t1();
函数的参数arguments
arguments对象,是实参的副本
//js 中 函数分为 : 形参,实参 function test ( a,b,c,d ) { // console.log( test.length ); //获取形参的个数 4 //函数的实际参数 内部就是使用一个数组去接收实际参数。 类数组对象 //arguments 对象,只能在函数内部使用。 //arguments 对象, 可以访问函数的实际参数 (实参的副本) // console.log( arguments.length ); //2 // console.log( arguments[0] ); //10 // // 第一种方式: // if ( test.length === arguments.length ) { // // return a + b; // // } //使用第二种 方式: if ( arguments.callee.length === arguments.length ) { return a + b; } //arguments对象, 使用得最多的还是使用递归操作 // arguments.callee; // 指向函数本身,函数体 } test(10,20);
this对象的简单理解
this对象是在运行时基于函数的执行环境绑定的
在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。
也就是说this关键字总是指代调用者(谁调用了我,就指向谁)。
//this: this对象是指在运行时期基于执行环境所绑定的 var k = 10; function test () { this.k = 20; } test(); console.log( test.k ); //undefined
call和apply方法
每一个函数都包含两个非继承而来的方法:call、apply。这俩个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
call、apply的用途之一就是传递参数,但事实上,它们真正强大的地方式能够扩充函数赖以运行的作用域(使你的作用域不断的去变化)。
使用call()、aplly()来扩充作用域的最大好处就是对象不需要与方法有任何耦合关系。
fn.call(obj);
让fn以运行,并且fn中的this以obj身份运行
将一个函数绑定到一个特定的作用域中,然后传递特定作用域中的参数。
//call, apply 简单 用法: 绑定一些函数 用于传递参数 调用 function sum ( x,y ) { return x + y; } function call1 ( num1,num2 ) { return sum.call(this,num1,num2); } function apply1 ( num1,num2 ) { return sum.apply(this,[num1,num2]) } //将一个函数绑定到一个特定的作用域中,然后传递特定作用域中的参数。 console.log( call1(10,10) ); console.log( apply1(20,10) );
//扩充作用域,底层也经常使用这两个方法,用于绑定不同的作用域。 //把一个函数赋给一个对象, 赋完之后,还可以重用,赋给另外一个对象。 window.color = 'pink'; var obj = { color: 'tan' } function showColor () { console.log( this.color ); } showColor.call(window); // showColor.call(this); showColor.apply(obj);
call方法简单的实现
function test1 ( a,b ) { return a + b; } //自定义对象 function Obj ( x,y ) { return x * y; } var obj = new Obj(); //挂载到对象上 obj.method = test1; //执行该函数 obj.method(10,20); //执行完后删除 delete obj.method;
bind
ES5中提供一个bind()方法。
为函数绑定一个执行时候的作用域。
将该方法绑定Function的原型上,因此定义一个function就有该方法,bind()添加作用域的时候,方法没有执行,在方法执行的时候,作用域会变成绑定兑现的作用域。
function b () { console.log(this.title); } function Book ( title ) { this.title = title; } var book = new Book('javascript'); // apply , call 特点:调用就执行 // b.call(book); // b.apply(book); // 当执行一个函数的时候更改作用域 var bBindfn = b.bind(book); // 执行更改作用域 bBindfn();
执行环境和作用域链概念
执行环境(execution context)是javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台执行它。
全局执行环境是最外围的一个执行环境。根据ECMScript实现所在的宿主环境不同,表示执行环境的对象也不一样。
每一个函数都有自己的执行环境。当执行流进一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返还给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问(控制代码的访问权限)。
var color1 = "blue"; function changeColor () { var color2 = "red"; function swapColor () { var color3 = color2; //color3 = 'red' color2 = color1; //color2 = 'blue' color1 = color3; //color1 = 'red' console.log( color1,color2,color3 ); } swapColor(); } changeColor(); //环境变量 可以一层一层的向上进行追溯 可以访问它的上级 环境(变量和函数) // 作用域链 具有层级关系 //在大型程序中,全局变量,尽量少使用,因为全局变量总是最后一次搜索。 防止全局变量污染。//很少去定义全局变量,效率比较慢。
垃圾收集和块级作用域的概念
垃圾收集
javascript是一门具有自动垃圾收集机制的编程语言。开发人员不必关心内存分配和回收问题。
垃圾回收器也是每隔一段时间去进行回收。
离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。标记清除是目前主流的垃圾收集算法。这种思想是给当前不使用的值加上标记,然后回收其内存。
//垃圾收集 ,标记清除 (模拟) function test () { var a = 10; //mark - 被使用 var b = 20; //mark - 被使用 } test(); //执行完毕 之后 ,a,b又被标记使用。 mark - 没有被使用 //在间隔时间中 回收。 如果mark 没有被使用, 则回收。
//引用计数(模拟) //如果变量被引用 , count = 1; function test2 () { var a = 10; //count = 1; var b = 20; var c; c = a; //count++ = 2; //a 被 c 所使用 ,引用。 a = 50; //count--; //重新被赋值 count-- //等待 count 为 0 的时候, 垃圾回收机制 就回收 }
块级作用域
javascript里面没有块级作用域的概念,所以在使用if、for时候要格外的小心。
javascript模拟块级作用域 (块级作用域,相当于内部的执行体,一个执行环境)
利用 IIEF的特性
//当函数执行之后, 变量就被回收 function test () { (function () { //函数有一个单独的作用域,外面无法访问到 i for ( var i=0; i<=5; i++ ) { console.log( i ); } })(); console.log( i ); //报错,无法找到 } test();
闭包 Closure
闭包与函数有着紧密的关系,它是函数的代码在运行过程中的一个动态环境,是一个运行期的、动态的概念。
所谓闭包,是指词法表示包括不必计算的变量的函数。也就是说,该函数能够使用函数外定义的变量。
在程序语言中,所谓闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。这些外部执行域的非持久型变量神奇地保留它们在闭包最初定义(或创建)时的值
理解闭包,必须要对于作用域链的概念非常的清楚。
var name = "xiao A"; var obj = { name : "xiao B", getName: function(){ return function(){ return this.name; } } }; console.log(obj.getName()()); //xiao A //类似: //var zf = obj.getName();//全局作用域 //zf();
var name = "xiao A"; var obj = { name : "xiao B", getName: function(){ var self = this; return function(){ return self.name; } } }; //console.log( obj.getName().call(obj) ); console.log( obj.getName()() ); //闭包: 一个函数, 可以访问另外一个作用域中的变量 //封闭性,(类似食品包装袋一样,封闭起来,保质期延长,变量的访问范围的延长) //private 起到一个保护变量的作用
//1 level function f(x){ //2 level var temp = x; //局部变量 //temp 标记 已经没有被使用 return function(x){ //3 level (function 有一个执行域) temp += x; //temp 下一级作用域仍然被引用 , 标记为 使用 alert(temp); } } //js 垃圾回收机制,当函数执行完毕后,内部所有的局部变量都集体的被回收。 var a = f(50); a(5); //55 a(10); //65 a(20); //85
回调函数
- 回调函数执行
- 回调函数中的this
- 回调函数的返回值
forEach
// forEach:用来遍历数组中的每一项 // 1. 数组中有几项,那么传递进去的匿名回调函数就需要执行几次。 // 2. 每一次执行匿名函数的时候,还传递了三个参数值:数组中的当前项item,当前项的索引index,原始的数组input // forEach方法中的this是arr,匿名函数回调函数的this默认是window var arr = [10, 234, 23, 76, 7666, 34]; arr.forEach(function(item, index, input) { input[index] = item * 10; // 操作之后,修改了原数组 console.log(arguments); }); var obj = {name: 'zf'}; // arr.forEach(function(item, index) { // console.log(this); // }.call(obj)); // 给forEach赋值的是时候,首先把匿名函数执行,把匿名函数中的this变为obj,把匿名函数执行的返回结果undefined赋值给foreach arr.forEach(function(item, index) { console.log(this, '---'); }.bind(obj)); // bind 只是预先处理,先把this转为参数的对象,到后续该执行的时候才执行。 // 不管是forEach,还是map都支持第二个参数,第二个参数表示:把匿名函数中的this进行修改。 arr.forEach(function() { console.log(this, 'this'); }, obj);
forEach兼容处理
// 兼容处理 Array.prototype._forEach = function(callback, context) { content = content || window; if ('forEach' in Array.prototype) { this.forEach(callback, context); return; } // IE6-8,执行回调逻辑 for (var i=0; i<this.length; i++) { callback || callback.call(context, this[i], i, this); } }
map
var arr = [10, 234, 23, 76, 7666, 34]; arr.map(function(item, index, input) { // 原有数组不变 console.log(arguments); return item * 10; }); // map和forEach非常相似,都是用来遍历数组中的每一项的值 // 区别:map的回调函数中支持return返回值,return的是什么,相当于把数组中的这一项改变为什么(但是并不影响原来的数组,只是相当于把原数组克隆一份,把克隆的这一份的数组中的对应项改变)
map兼容处理
Array.prototype._map = function(callback, context) { context = context || window; if ('map' in Array.prototype) { this.map(callback, context); return; } // IE6-8,执行回调逻辑 var resArr = []; for (var i=0; i<this.length; i++) { if (typeof callback === 'function') { resArr[resArr.length] = callback.call(context, this[i], i, this); } } return resArr; } var arr = [10, 234, 23, 76, 7666, 34]; arrMap = arr._map(function(item, index, input) { // 原有数组不变 // console.log(arguments, '_map'); return item * 100; }); console.log(arrMap);
柯理化函数思想
柯理化函数思想:一个JS预处理思想
核心:利用函数执行可以形成一个不销毁的私有作用域的原理,把需要预先处理的内容都存储在这个不销毁的作用域中,并且返回一个匿名函数,执行的都是匿名函数,把匿名函数中,把之前的预先存储的值进行相关操作处理即可。
var obj = {name: 'zf'}; function fn(num1, num2) { console.log(this, num1, num2) } // 把传递进来的callback这个方法中的this预先处理为context function bind(callback, context) { context = context || window; var outArg = Array.prototype.slice.call(arguments, 2); return function(ev) { var innerArg = Array.prototype.slice.call(arguments, 0); callback.apply(context, outArg.concat(innerArg)); } } // document.body.onclick = fn; // fn 中的 this是document.body. num1是 MouseEven对象 document.body.onclick = fn.bind(obj, 100, 200); // 除了预先处理了this和需要手动传递的参数值以外,把浏览器默认给传递的鼠标事件对象也进行预先处理,传递到最后一个参数。 document.body.onclick = bind(obj, 100, 200) // document.body.onclick = function() { // console.log(this); // this --> document.body // } // window.setTimeout(fn.bind(obj), 0); // window.setTimeout(bind(fn, obj, 100, 20), 0); // 给定时器绑定方法,然后定时器到达时间的时候,让fn执行,并且让fn中的this变为obj
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。