JavaScript基础(四)

一、函数

1)函数定义及调用

  函数有三种定义方式,除了常见的函数声明和函数表达式,一个函数就是一个Function对象,我们也可以使用Function的构造函数来创建一个新函数。

  • 函数声明
var name = 'mary'
function foo(name) {
	name = 'mike'
	console.log(name)
}
//调用
foo(name)  //输出:mike
console.log(name)  //输出:mary

  可以看到,当一个值被作为参数传递给函数,函数内部对该参数的改变不影响原值。

  • 函数表达式
//匿名函数
let foo = function(name) { 
	console.log(name)
}
//提供函数名的函数
let func = function foo(name) { 
	console.log(name)
}
//-----------------
var print = function(f, str) {
	f(str)
}
print(func, 'jack')

  如上实例所展示,表达式定义法可以创建匿名函数和具名函数,当我们会把一个函数作为变量进行传递时,函数表达式定义法就比较好用了。

  • new Function()
function foo(x, y) {
	console.log(x+y)
}
//上边的函数就等同于  (以下三种写法都正确)
var foo = new Function('x', 'y', 'console.log(x+y)')
var foo = new Function('x, y', 'console.log(x+y)')
var foo = new Function('x,y', 'console.log(x+y)')

  函数一定要处于调用它们的域中,但是函数的声明可以被提升 (出现在调用语句之后),不过需要注意的是,函数提升仅适用于声明式函数,不适用于表达式函数。

----------关于几种定义的区别---------

  1. 声明式函数有函数提升,其他的没有。
  2. 第一种定义方式,函数声明的过程中创建了一个和函数名相同的变量,我们可以通过函数名访问定义的函数;而第二种函数表达式的定义方式,函数名和被赋值的变量名毫无关系,并且不能在函数外部通过函数名访问该函数,会报错undefined。
function foo1(){
	console.log('foo1')
}
foo1  //打印结果:ƒ foo1(){ console.log('foo1') }
var func = function foo2() {
	console.log('foo1')
}
func //打印结果:ƒ foo2(){ console.log('foo2') }
foo2 //打印结果:Uncaught ReferenceError: foo2 is not defined
  1. 通过Function构造函数定义的函数是没有名称的。

----------有时候看似是函数声明,其实是函数表达式---------

// 函数表达式
(function bar() {})
// 函数表达式
x = function hello() {}
if (x) {
   // 函数表达式
   function world() {} //非source element
}

// 函数声明
function a() {
   // 函数声明
   function b() {}
   if (0) {
      //函数表达式
      function c() {} //非source element
   }
}

函数声明在一些意外的情况下可能转换成函数表达式:

  1. 成为表达式的一部分;
  2. 不再是函数或者脚本自身的“源元素”(source element,是脚本或函数体中的非嵌套语句)。

2)函数作用域

  会在下一篇详细介绍作用域和作用域链的时候一起介绍。

3)闭包

1、什么是闭包?

  闭包是由函数以及声明该函数的词法环境组合而成的,该环境包含了这个闭包创建时作用域内的任何局部变量。嵌套函数为闭包的形成提供了条件。也就是说,内部函数可以访问外部函数的作用域,而外部函数无法访问内部函数的参数和变量。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

function makeAdder(x) {
  return function add(y) {
    console.log(x+y, this.x+y)
  };
}
var x = 50
var add5 = makeAdder(5)
var add10 = makeAdder(10)
console.log(add5(2))  // 7 52  
console.log(add10(2)) // 12 52
  1. 内部函数add()在执行前从外部函数被返回,我们很容易认为,外部函数makeAdder()的局部变量x只存在于该函数执行期间,在其执行完后就不能再被访问了,所以执行add5()和add10()的时候,打印x+y会报错。但是结果是还能正常访问,为什么?
      执行过程中该嵌套函数形成了闭包,闭包包含外部函数的词法环境,词法环境存储的是外部函数的任何局部变量。创建的内部函数实例add5和add10中,有对这个词法环境的引用,因此他们执行的时候能通过引用找到目标变量。

  2. add5和add10的执行结果不一样,尽管是通过同一个嵌套函数执行得到的实例,但是两者之间没有关系。
      每执行一次外部函数都会形成一个独立的闭包,然后返回的内部函数实例的执行,仅仅是基于其独立的闭包。从本质上讲,makeAdder 是一个函数工厂 ,每调用一次他就创建一个新函数。

2、闭包的使用

1. 多个对象调用同一个方法对同一个变量进行操作的时候。

//如果有三个不同的按钮,点击分别给变量globalVal赋值10,20,30
var globalVal = 1
function changeVal(val) {
	return function setVal() {
		globalVal = val
	}
}
var setVal1 = changeVal(10)
var setVal2 = changeVal(20)
var setVal3 = changeVal(30)
//然后给三个按钮的点击事件分别绑定setVal1、setVal2、setVal3

2. 模拟私有方法。

var obj = (function() {
	var num = 0
	function change(val) {
		num += val
	}
	return {
		increment: function() {
      		change(1)
    	},
    	decrement: function() {
      		change(-1)
    	},
    	value: function() {
			return num
		}
	}
})();
console.log(obj.value()) //0
obj.increment()
console.log(obj.value()) //1
obj.decrement()
console.log(obj.value()) //0

  上边的函数示例中,通过一个立即执行的匿名函数只创建了一个词法环境,三个返回的公共函数共享该此法环境。词法环境中包含一个私有变量num和一个私有函数change(),这两个私有项都是不能在匿名函数外边直接访问的,只能通过三个公共函数访问。

------这里补充一下访问函数内部变量的方法------
  a. 通过return返回

function foo(param) {
	var str = 'sss'
	return str+param
}
console.log(foo('yyy')) //sssyyy

  b. 通过闭包返回

function foo() {
	var str = 'sss'
	return function(param) {
		return str+param
	}
}
let func = foo()
console.log(func('yyy')) //sssyyy

3. 在循环中的使用

经典错误示范:

//省略html内容,直接看js代码吧
function showHelp(help) {
  //获取id为help的元素,设置元素显示的内容
  document.getElementById('help').innerHTML = help
}
function setupHelp() {
  //三个input的id以及获取焦点后各自需要显示的信息
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
  ]
  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i]
    //onfocus方法回调改变显示内容的事件
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help)
    }
  }
}
setupHelp()

  分析上面一段代码,执行结果会是什么样子呢?
  答案是:三个input的onfocus回调时,显示的信息都是关于年龄的信息Your age (you must be over 16)。
  原因是,onfocus事件绑定的函数本身和setupHelp()函数的词法环境形成了一个闭包,被绑定函数中访问了词法环境中的item变量。请注意,这里三个不同的input访问的是同一个词法环境!
  由于变量item是var声明的、可覆盖的,其作用域不是块级作用域而是函数setupHelp()中最高层的作用域。循环中只是给onfocus绑定了函数并没有执行,所以循环完成的时候,item变量最终被赋值为helpText[2],当用户触发该事件的时候才会执行相应的showHelp()函数,但是词法环境中的item变量已经变成最后一个了。

  解决方法:可以使用const声明item变量,这样每个闭包都绑定的块作用域的item变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值