函数的定义方式
函数声明(自定义函数)命名函数
function foo () {
}
函数表达式 匿名函数
var foo = function () {
}
new Function
var fn = new Function('var name = "zs";console.log(name)');
Function里面的参数都必须是字符串的形式
这种new的方法效率低,不方便,使用较少。
所有函数都是 Function
的实例
函数也是对象
<script>
//1.
function fun() {}
//2.
var fun = function() {}
//3.
var f = new Function('a','b','console.log(a+b)');
f(1,2);
console.dir(f)
</script>
函数声明与匿名函数的区别
1.函数声明必须有名字
2.函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
3.匿名函数类似于变量赋值
4.匿名函数可以没有名字
5.匿名函数没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用
函数的调用方式
<script>
//1.普通函数
function fn() {
console.log('牛马');
}
fn();
fn.call();
//对象的方法
var o = {
saihi:function() {
console.log('牛马');
}
}
o.saihi();
//构造函数
function Star() {}
var star = new Star();
//绑定事件函数
btn.click = function() {} //绑定的btn自己调用
//定时器函数
setInterval(function() {},1000); //定时器每隔一秒调用一次
//立即执行函数
(function() {
console.log('牛马'); //自动调用
})();
</script>
函数内this指向的不同场景
<script>
//1.普通函数
function fn() {
console.log('函数的this指向' + this);
}
window.fn();
fn.call();
//this指向window
//对象的方法
var o = {
saihi:function() {
console.log('函数的this指向' + this);
}
}
o.saihi();
//this指向 o 这个对象
//构造函数
function Star() {}
var ldh = new Star();
Star.prototype.sing = function() {
}
//构造函数的this指向ldh这个实例对象 原型对象里的this也指向ldh这个对象
//绑定事件函数
btn.click = function() {} //绑定的btn自己调用,this指向btn
//定时器函数
wicdow.setInterval(function() {},1000); //定时器每隔一秒调用一次 this指向window
//立即执行函数
(function() {
console.log('函数的this指向' + this); //自动调用 this指向window
})();
</script>
call、apply、bind
JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有
bind()
call()
apply()
call
call() 方法调用一个函数,其具有一个指定的this值和分别地提供的参数(参数的列表)
注:该方法的作用和 apply() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。
语法
fun.call(thisArg, arg1, arg2, ...)
参数
thisArg
在 fun 函数运行时指定的 this 值
如果指定了 null 或者 undefined 则内部 this 指向 window
arg1, arg2, …
指定的参数列表
<script>
//call 第一个作用可以调用函数
function fun() {
console.log(this)
}
var o = {
name:'andy'
}
//call 第二个作用可以改变this指向
fun.call(o);
//call 主要作用用于继承
function Father(name,age) {
this.name = name;
this.age = age
}
function Son(name,age) {
Father.call(this,name,age);
}
var son = new Son('lihua',22);
console.log(son);
</script>
apply
apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。
语法:
fun.apply(thisArg, [argsArray])
例如:
fun.apply(this, ['eat', 'bananas'])
<script>
//apply 第一个作用可以调用函数
function fun(arr) {
console.log(this)
console.log(arr); //blue
}
var o = {
name:'andy'
}
//apply 第二个作用可以改变this指向
fun.apply(o,['red']);
//apply其他参数必须是一个数组(伪数组)
//apply应用:可以使用数学内置函数求最大值
var arr = [1,22,5,11,8];
console.log(Math.max.apply(Math,arr)); //22
</script>
bind
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。
一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
参数
thisArg
当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
arg1, arg2, …
当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
返回值:返回由指定的this值和初始化参数改造的原函数拷贝。
<script>
//bind 不可以调用函数
function fun(a,b) {
console.log(this)
console.log(a+b);
}
var o = {
name:'andy'
}
//bind 可以改变this指向
var f = fun.bind(o,1,2);
//返回的是原函数改变this后产生的新函数
f();
//如果我们不想立即调用函数 但是想改变它的内部this指向 此时需要bind方法
var btn = document.querySelector('button');
btn.onclick = function () {
this.disabled = true;
setTimeout(function() {
this.disabled = false
}.bind(this),3000)
}
//正常情况下 定时器的this指向window 通过bind改变this指向btn
</script>
小结
-
共同点 : 都可以改变this指向
-
不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
- bind 不会调用函数, 可以改变函数内部this指向.
-
应用场景
1.call 经常做继承.
2. apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
严格模式
什么是严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。
ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法
为未来新版本的 Javascript 做好铺垫。
比如一些保留字如:class,enum,export, extends, import, super 不能做变量名
开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为**脚本开启严格模式和为函数开启严格模式**两种情况。
情况一 :为脚本开启严格模式
- 有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他
script 脚本文件。
<script>
//为某个函数添加严格模式
'use strict'
//下面的js代码 会按照严格模式执行
</script>
<script>
(function () {
'use strict';
})();
</script>
情况二: 为函数开启严格模式
- 要给某个函数开启严格模式,需要把“use strict”; (或 ‘use strict’; ) 声明放在函数体所有语句之前。
<script>
//为某个函数添加严格模式
function fn() {
'use strict'
//下面代码会按照严格模式执行
}
function fun() {
//这里的还是按照普通模式执行
}
</script>
严格模式的变化
变量:
在正常模式中,如果一个变量没有声明就赋值,默认是全局变量.
严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用
严禁删除已经声明变量。例如,delete x; 语法是错误的
this 指向问题:
严格模式下全局作用域中函数中的 this 是 undefined
严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果赋值,会报错
new 实例化的构造函数指向创建的对象实例。
定时器 this 还是指向 window
事件、对象还是指向调用者
函数变化
函数不能有重名的参数
函数必须声明在最前面.新版本的JavaScript会引入“块级作用域”
( ES6 中已引入)为了与新版本接轨,不允许在非函数的代码块内声明函数
代码演示:
'use strict'
num = 10
console.log(num)//严格模式后使用未声明的变量
--------------------------------------------------------------------------------
var num2 = 1;
delete num2;//严格模式不允许删除变量
--------------------------------------------------------------------------------
function fn() {
console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined
}
fn();
---------------------------------------------------------------------------------
function Star() {
this.sex = '男';
}
// Star();严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错.
var ldh = new Star();
console.log(ldh.sex);
----------------------------------------------------------------------------------
setTimeout(function() {
console.log(this); //严格模式下,定时器 this 还是指向 window
}, 2000);
function fn(a,a) {
console.log(a+a);
}
fn(1,2);//不被允许 因为a重名
函数的其他成员
arguments:实参集合
caller:函数的调用者
length:形参的个数
name:函数的名称
function fn(x, y, z) {
console.log(fn.length) // => 形参的个数
console.log(arguments) // 伪数组实参参数集合
console.log(arguments.callee === fn) // 函数本身
console.log(fn.caller) // 函数的调用者
console.log(fn.name) // => 函数的名字
}
高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../jquery.min.js"></script>
<style>
div{
width: 200px;
height: 200px;
background-color: black;
position: absolute;
}
</style>
</head>
<body>
<div></div>
<script>
function fn(a,b,callback) {
console.log(a+b);
callback && callback();
}
fn(1,2,function() {
console.log('我是最后调用的');
})
$("div").animate({
left:800
},function() {
$('div').css("height","1000")
})
</script>
</body>
</html>
函数闭包
什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 “定义在一个函数内部的函数”。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
在 chrome 中调试闭包
- 打开浏览器,按 F12 键启动 chrome 调试工具。
- 设置断点。
- 找到 Scope 选项(Scope 作用域的意思)。
- 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
- 当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
为什么会有闭包
由于作用域的原因,我们无法在函数外访问函数里面定义的变量,但有时候我们又会有这样的需求,这个时候我们就需要使用闭包了。
闭包的用途
可以在函数外部读取函数内部成员
让函数内成员始终存活在内存中
延伸变量的作用范围。
<script>
//闭包的主要作用:延伸了变量的作用范围
//正常情况下 f无法访问fn内部变量
//通过闭包 f可以访问fn内部的局部变量num
function fn() {
var num = 10;
return function() {
console.log(num);
}
}
var f = fn();
// 类似于
// var f = function() {
// console.log(num)
// }
f();
</script>
闭包案例
1.点击li标签显示索引
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="nav">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script>
//1.利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li')
for(var i = 0;i < lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function() {
console.log(this.index);
}
}
//2.利用闭包的方式
for(var i = 0;i < lis.length; i++) {
//利用for循环创建4个立即执行函数
//立即执行函数也成为小闭包 因为立即执行函数里面的任何一个函数都可以使用它的li变量
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i)
}
//当循环开始
//i为0 会把它传给立即执行函数里的i
//然后立即执行函数里的i把他传到立即执行函数function的i里
//lis[i] 拿到i
//然后传递到console.log(i)里
</script>
</body>
</html>
2.定时器显示li标签内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="nav">
<ul>
<li>柚子</li>
<li>柠檬</li>
<li>西瓜</li>
<li>草莓</li>
</ul>
</div>
<script>
var lis = document.querySelector('.nav').querySelectorAll('li')
//利用闭包的方式
for(var i = 0;i < lis.length; i++) {
//利用for循环创建4个立即执行函数
//立即执行函数也成为小闭包 因为立即执行函数里面的任何一个函数都可以使用它的li变量
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
},2000)
})(i)
}
</script>
</body>
</html>
效果:
3.打车案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var car = (function() {
var start = 13;
var total = 0;
return {
price:function(n) {
if(n<3) {
total = 13;
}else {
total = start + (n-3) * 5;
}
return total;
},
//拥堵之后
yd:function(flag) {
return flag ? total + 10 : total;
}
}
})();
console.log(car.price(5)); //23
console.log(car.yd(true)); //33
console.log(car.price(1)); //13
console.log(car.yd(false)); //13
</script>
</body>
</html>
闭包的好处与坏处
好处
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
坏处
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。
闭包理解参考闭包
思考
<script>
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()())
//object.getNameFunc()()
//类似于
//var f = function() {return this.name} f();
//即 function() {this} () 这里相当于立即执行函数 this指向window 所以输出结果为The window
</script>
这里没有闭包 因为函数内没有任何局部变量,没有调用。
2.
<script>
var name = "The Window";
var object = {
name: "",
getNameFunc: function () {
var that = this;
//这里的this指向object 并把它存到that中
return function () {
return that.name;
//这里that指向object 返回object的name My Object
};
}
};
console.log(object.getNameFunc()()) //输出结果为My Object
//类似于 var f = object.getNameFunc() var f = function () {return that.name;} f();
</script>
这里存在闭包 因为that为局部变量 且
return function () {
return that.name;
//这里that指向object 返回object的name My Object
};
调用了that。