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

回调函数

  1. 回调函数执行
  2. 回调函数中的this
  3. 回调函数的返回值

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程序设计有所帮助。

(0)

相关推荐

  • js函数内变量的作用域分析

    本文实例分析了js函数内变量的作用域.分享给大家供大家参考.具体分析如下: 先看一个函数实例: 复制代码 代码如下: <html> <head> </head> <body> <script type="text/javascript"> var a = 5; var c = 3; function t(){  var a = 6;  var b = 10;  document.write(a+'-----'+b);  doc

  • javascript 函数及作用域总结介绍

    在js中使用函数注意三点:1.函数被调用时,它是运行在他被声明时的语法环境中的: 2.函数自己无法运行,它总是被对象调用的,函数运行时,函数体内的this指针指向调用该函数的对象,如果调用函数时没有明确指定该对象, this 默认指向 window ( strict 模式除外,本文不涉及 strict 模式): 3.函数是一种带有可执行代码的对象类型数据. 一.声明函数 1.使用 function 关键字 复制代码 代码如下: function myfun(a,b){ //声明名为myfun的函

  • 深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

    函数表达式 1.JavaScript中定义函数有2钟方法: 1-1.函数声明: 复制代码 代码如下: function funcName(arg1,arg2,arg3){  //函数体} ①name属性:可读取函数名.非标准,浏览器支持:FF.Chrome.safari.Opera. ②函数声明提升:指执行代码之前会先读取函数声明.即函数调用可置于函数声明之前. 1-2.函数表达式: 复制代码 代码如下: var funcName = function(arg1,arg2,arg3){  //函

  • JavaScript匿名函数之模仿块级作用域

    匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途. 匿名函数:就是没有函数名的函数. 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种:这也是最常规的一种 function double(x){ return 2 * x; } 第二种:这种方法使用了Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用. var double = new Function('x', 'return 2 * x;'); 第三种: var

  • js使用函数绑定技术改变事件处理程序的作用域

    第一种,也是 最常见的,就是直接在html标签里面通过指定事件处理程序同名的HTML属性来注册事件,代码如下: 复制代码 代码如下: function eventHandler() { alert("当前作用域是 input 元素本身"); } <input type="button" value="单击我" onclick="eventHandler(this)"/> 第二种方式就是将一个函数赋值给一个事件处理程

  • js 函数的执行环境和作用域链的深入解析

    第一步. 定义后:每个已定义函数,都有一个内在属性[scope],其对应一个对象的列表,列表中的对象仅能内部访问. 例如:建立一个全局函数A,那么A的[Scope]内部属性中只包含一个全局对象(Global Object),而如果我们在A中创建一个新的函数B,那么B的[Scope]属性中就包含两个对象,函数A的Activation Object对象在前面,全局对象(Global Object)排在后面. 简而言之,一个函数的[Scope]属性中对象列表的顺序是上一层函数的Activation O

  • 关于javascript 回调函数中变量作用域的讨论

    1.背景 Javascript中的回调函数,相信大家都不陌生,最明显的例子是做Ajax请求时,提供的回调函数, 实际上DOM节点的事件处理方法(onclick,ondblclick等)也是回调函数. 在使用DWR的时候,回调函数可以作为第一个或者最后一个参数出现,如: JScript code function callBack(result){ } myDwrService.doSomething(param1,param2,callBack);//DWR的推荐方式 //或者 myDwrSer

  • javascript的函数作用域

    在一些类似c语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明他们的代码段之外是不可见的,我们称为块级作用域(block scope),而javascript中没有块级作用域.取而代之的javascript使用的是函数作用域(function scope):变量在声明它的函数体以及这个函数体嵌套的任意函数体内都是有定义的. 在如下代码中,在不同位置定义的i,j和k,他们再同一个作用域内都是有定义的 复制代码 代码如下: function text(o)   {      

  • javascript函数作用域学习示例(js作用域)

    在一些类似c语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明他们的代码段之外是不可见的,我们称为块级作用域(block scope),而javascript中没有块级作用域.取而代之的javascript使用的是函数作用域(function scope):变量在声明它的函数体以及这个函数体嵌套的任意函数体内都是有定义的. 在如下代码中,在不同位置定义的i,j和k,他们再同一个作用域内都是有定义的 复制代码 代码如下: function text(o)   {      

  • 浅谈JavaScript的函数及作用域

    函数和作用域是JavaScript的重要组成部分,我们在使用JavaScript编写程序的过程中经常要用到这两部分内容,作为初学者,我经常有困惑,借助写此博文来巩固下之前学习的内容. (一)JavaScript函数 JavaScript函数是指一个特定代码块,可能包含多条语句,可以通过名字来供其他语句调用以执行函数包含的代码语句. 1.JavaScript创建函数的方法有两种: 函数声明: function funcDeclaration(){ return 'A is a function';

  • 详谈JavaScript 匿名函数及闭包

    1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没有函数名的函数. 1.1 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种:这也是最常规的一种 复制代码 代码如下: function double(x){     return 2 * x;   } 第二种:这种方法使用了Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用. 复制代码 代码如下: var double = new Functio

  • JavaScript函数作用域链分析

    本文实例分析了JavaScript函数作用域链.分享给大家供大家参考.具体分析如下: 作用域链: JavaScript的每个函数function都有自己的作用域,使用Active Object(简称AO)活动对象来保存,在相互嵌套的函数中形成了作用域链,如下图所示: 作用域链就是从里到外的AO链 变量的寻找: 函数fn3中使用的变量,如在fn3作用域内寻找不到,则往外层fn2作用域寻找,以此类推,直到全局对象window 代码演示如下: var c = 5; function t1(){ var

随机推荐