函数高级
函数的定义的三种方式
- 声明方式(匿名函数)
- new Function()方式(不常用)
- 使用call调用
- 对象的方法
- 构造函数
- 绑定事件函数
- 定时器函数
- 立即执行函数
//函数的定义的三种方式
//定义方式
function fn() {};
fn();
//声明方式(匿名函数)
var func = function() {};
func();
//new Function()方式(不常用)
var f = new Function('a', 'b', 'console.log(a+b)'); //a,b 为形参 Function参数都为字符串格式,效率低
f(1, 2);
//所有的函数都是 Function 的实例(对象)
//函数也属于对象
console.log(fn instanceof Object);
console.log(fn.__proto__.constructor); //函数的原型对象的构造函数就是Function
//函数的调用方式
fn(); //常规调用方式
fn.call(); //使用call调用
//对象的方法
var o = {
fnfn: function() {
console.log('ddd');
}
};
o.fnfn();
//构造函数
function Star() {}
new Star();
//绑定事件函数
document.onclick = function() {};
//定时器函数
setInterval(function() {}, );
//立即执行函数
(function() {})();
函数的this指向
- 普通函数
- 对象方法
- 绑定事件函数
- 定时器函数
- 立即执行函数
//普通函数
function fn11() {
console.log('普通函数的this' + this) //指向window
}
fn11();
//对象方法
var o = {
fnfn: function() {
console.log('对象方法this', this); //指向对象本身
}
};
//构造函数 this 指向实例对象 指向kano这个实例对象
function Star() {}
Star.prototype.sing = function() {
console.log(this);
}; //原型对象的this指向的也是kano这个实例对象
var kano = new Star();
kano.sing();
//绑定事件函数 this指向的是函数的调用者 document这个对象
document.onclick = function() {
console.log('绑定事件函数的this' + this);
};
//定时器函数
setTimeout(function() {
console.log("定时器的this" + this); //还是指向window
}, 1000);
//立即执行函数
(function() {
console.log('立即执行函数this' + this) //也是指向window
})()
总结:this指向,是当我们调用函数的时候决定的,调用方式的不同决定了this指向不同 ,一般是指向我们的调用者
调用方式 | this指向 |
---|---|
普通函数的调用 | window |
构造函数的调用 | 实例对象,原型对象里面的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
改变函数内部的指向
- call方法 调用一个对象,简单理解为调用函数的方式,但是它可以改变这个函数的this指向
- apply方法
- bind方法
语法
function.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [argsArray])
func.bind(thisArg, arg1, arg2, arg3.....)
- thisArg:在fun函数运行时指定的this值
- argx :传递的其他参数
示例
bind方法
//call方法 调用一个对象,简单理解为调用函数的方式,但是它可以改变这个函数的this指向
var o = {
name: 'kano'
}
function fn(a, b) {
console.log(this)
console.log(a + b);
}
fn.call(o, 1, 2);
//call的主要作用可以实现继承
function Father(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
function Son(name, age, sex) {
Father.call(this, name, age, sex)
}
var son = new Son("kano", 18, '女');
console.log(son);
apply方法
//apply方法
var o1 = {
name: 'kano'
};
function fn(arr) {
console.log(this);
console.log(arr);
}
fn.apply(o1, ['我是字符串']);
//1. 也是调用函数 第二个可以改函数内部的this指向
//2. 但是他的参数必须是数组或者伪数组
//下面的方法使用 apply 方法寻找一个数值数组中的最大元素。 Math.max.apply([1, 2, 3]) 等价于 Math.max(1, 2, 3), 但是你可以使用 Math.max.apply 作用于任意长度的数组上。
var max = Math.max.apply(null, [1, 2, 2, 1, 2, 1, 4, 23, 14, 32, 15, 43, 512, 43, 16, 32, 6, 32, 5, 43, 25]); //这里不需要传递对象进去就可以写null,但是这里推荐写Math,具体原因参考后面的严格模式
//最小值同理
//注意:虽然apply传递的是一个数组的形式,但是他进入函数时候会把数组依次转换成我们想要的格式
console.log("最大值是:" + max);
//高效率的push方法
var arr1 = [1, 2, 3, 4, 5];
var arr2 = [6, 7, 8, 9, 10];
arr1.push.apply(arr1, arr2);
console.info(arr1);
bind方法
//bind方法
//bind方法不会调用函数,但是能改变函数内部的this指向
//fun.bind(thisArg, arg1, arg2, arg3.....)
//thisArg:在fun函数运行时指定的this值
//argx :传递的其他参数
//返回由指定的this和初始化参数改造的 原函数拷贝
//就相当于我用自己的参数绑定了那个函数,由于是先拷贝后改变,所以原函数其实是不变的
function fn2(a, b) {
console.log("bind:" + this + (a + b));
}
var fncp = fn2.bind(o, 2, 3);
console.log(fncp);
fncp(1, 2); //bind:[object Object]5 //因为bind中使用参数之后就变成了被预置入绑定函数的参数列表,因此无法再次进行修改
fn2(1, 2); //bind:[object window]3
//bind方法不会调用原来的函数 可以改变原来函数内部的this指向
//返回的是原函数改变this之后产生的新函数
//例子:我们有一个按钮,当我们点击之后,就禁用这个按钮,3秒之后再开启这个按钮
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
this.disabled = true;
setInterval(function() {
this.disabled = false;
}.bind(this), 3000); //这个this指向的是btn这个对象 在函数外面绑定一个bind,指向btn,就能解决定时器里面this指向问题
}
}
总结
- 相同点:都可以改变函数内部的this的指向
- 不同点: call 和apply都会调用函数,但是他们传递的参数不一样,call传递参数arg1,arg2...形式 apply必须是数组形式[arg]
- 主要应用场景:
- call经常做继承
- apply和数组有关系
- bind 不调用函数,但是想改变函数内部this指向
严格模式
什么是严格模式
JavaScript除了提供正常模式外,还提供了严格模式( strict mode )。ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行JS代码。
严格模式在IE10以上版本的浏览器中才会被支持,l旧版本浏览器中会被忽略。严格模式对正常的JavaScript语义做了一些更改∶
- 消除了Javascript语法的一些不合理、不严谨之处,减少了一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的Javascript做好铺垫。比如一些保留字如:class, enum, export, extends, import, super不能做变量名
开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
- 为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句:
“use strict”;(或‘use strict' ;)。或者直接放在立即执行函数里面
- 为函数开启严格模式
要给某个函数开启严格模式,需要把“use strict”;(或'use strict';)声明放在函数体所有语句之前。
function fn() {
'use strict';
//下面的代码按照严格模式执行
}
function fun() {
//里面的还是按照普通模式执行
}
严格模式中的变化
严格模式对Javascript的语法和行为,都做了一些改变。
在正常横式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用。
function fff() {
'use strict';
num = 10;
console.log(num);
}
fff();//报错,变量没有申明
在严格模式中严禁删除已经申明的变量
var kano = 1224;
delete kano; //报错,无法在严格模式中删除已经申明好的变量
严格模式下this指向问题
以前在全同作用域函数中的this指向window对象。
严格模式下全局作用域中函数中的this是undefined。以前构造函数时不加new也可以调用,当普通函数,this指向全局对象严格模式下,如果构造函数不加new调用, this会报错.
new 实例化的构造函数指向创建的对象实例。定时器this还是指向window 。
事件、对象还是指向调用者。
(function(){
'use strict';
function ffff() {
console.log(this);//undefined
}
ffff();
//在正常模式下构造函数可以当普通函数调用
function Kano() {
this.age = 22
} //严格模式下this指向的是undefined
Kano();
console.log(window.age); //严格模式下报错
var kano = new Kano();
console.log(kano.age); //可以调用 显示22
//定时器的this指向的还是window
setTimeout(function(){
console.log(this);
},1000);
})()
函数变化
函数不能有重名的参数
函数必须声明在顶层新版本的JavaScript 会引入“块级作用域”(ES6中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。
//在普通模式下参数可以重名
//a=1
//a=2
function aa(a, a) {
console.log(a + a);//2+2=4
}
aa(1, 2); //结果是4
//但在严格模式下会直接报错Duplicate parameter name not allowed in this context
更多严格模式详情参考MDN严格模式
高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
<script>
function fn(callback){
callback && callback();
}
fn (function(){
alert( "kanokano" )
}
</script>
//高阶函数-函数可以作为参数传递
function fn(a, b, callback) {
console.log(a + b);
callback && callback();
};
fn(1, 2, function() {
console.log("OK");
});
在jQuery中的应用
$('div').animate({
left: 500
},function(){//回调
$("div").hide();
});
闭包
什么是闭包
闭包( closure )指有权访问另一个函数作用域中变量的函数。----- JavaScript高级程序设计
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量 就叫闭包
function fn() {
var num = 199;
function fun() {
console.log(num);
}
}
以上函数就是一个闭包,因为fun函数访问了fn函数里面的局部变量
闭包的作用
function fn() {
var num = 199;
return function() {
console.log(num);
}
}
var f = fn();
f();
fn的调用,返回了function匿名函数,而匿名函数里面有fn的闭包,就可以实现在全局作用域下使用局部作用域的变量.
此时 num变量会在f函数全部执行完后才会销毁
闭包的主要作用,就是延申了变量的作用范围
利用闭包的方式得到li的当前索引号
var lis = document.querySelector("ul").querySelectorAll("li");
for (var i = 0; i < lis.length; i++) {
//利用for循环创建了四个立即执行函数
(function(i) { //参数传入
lis[i].onclick = function() {
console.log(i);
}
})(i); //参数传入
}
闭包应用-1秒钟之后,打印li中的元素内容
var lis = document.querySelector("ul").querySelectorAll("li");
for (var i = 0; i < lis.length; i++) {
(function(i) { //参数传入
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 1000);
})(i); //参数传入
}
立即执行函数也成为小闭包,因为立即执行函数里面的任何一个函数都可以使用他的变量
在某些情况下,闭包效率可能会比较低
闭包应用-计算打车价格
打车起步价13(3公里内),之后每多一公里增加5块钱.用户输入公里数就可以计算打车价格//如果有拥堵情况,总价格多收取10块钱拥堵费(这要价太黑了( )
//利用闭包计算租车价格
var car = (function() {
var start = 13; //起步价
var total = 0; //总价
return {
//正常的总价
price: function(n) {
if (n <= 3) {
total = start;
} 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
思考题
如下,返回值是?
//思考题
var name = "Window";
var obj = {
name: "obj",
getName: function() {
return function() {
return this.name;
};
}
};
console.log(obj.getName()()); //window
//obj.getName()()
//相当于 var f = function() {
// return this.name;
// };
//所以this自然指向window
//而name变量是在window作用域内,所以是‘window’
函数里面没有局部变量,所以没有闭包的产生
结果为window
如果想使用obj的name,那就需要在函数内使用变量保存obj,这样才能产生闭包
var name = "Window";
var obj = {
name: "obj",
getName: function() {
var that = this;//产生闭包
return function() {
return that.name;
};
}
};
console.log(obj.getName()()); //obj
闭包总结
- 闭包是一个函数(一个作用域可以访问另外一个函数的局部变量)
- 闭包可以延申变量的作用域范围
递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
递归函数作用和循环效果是一致的
利用递归解决汉诺塔问题
function H(n, one, two, three) {
if (n == 1) {
console.log(one + " -> " + three);
} else {
H(n - 1, one, three, two);
console.log(one + " -> " + three);
H(n - 1, two, one, three);
}
}
H(3, 'a', 'b', 'c');
利用递归遍历对象属性
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '冰箱1'
}, {
id: 122,
gname: '洗衣机1'
}]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
//输入id号,返回数据对象
function getID(json, id) {
var o = {}; //创建一个空对象用于保存查找到的数据
json.forEach(function(item) {
if (item.id == id) {
o = item; //给数据赋值
return item; //返回查找到的数据给上层递归函数
} else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id); //递归,并准备接受返回值
}
})
return o;
}
console.log(getID(data, 1));
console.log(getID(data, 11));
console.log(getID(data, 111));