函数的定义和调用
1.函数的定义方式
(1)函数声明方式function关键字(命名函数)
function fn(){};
(2)函数表达式(匿名函数)
var fun = function(){};
(3)new Function()
var fn = new Function('参数1','参数2'...,'函数体')
var f = new Function('a','b','console.log(a + b)');
-
Function里面参数和函数体都必须是字符串格式。
-
第三种方式执行效率低(因为要先转换为js能识别的js代码,即前两种方式,然后再执行,因此多了解析这一步,执行效率更低),不方便书写,因此较少使用。
-
所有函数都是Function的实例(对象)。
-
函数也属于对象。
-
函数的原型三角关系如下:
2.函数的调用方式
(1)普通函数
function fn(){
console.log('Hello!');
}
fn();//或 fn.call()
(2)对象的方法
var a = {
say: function(){
console.log('Hello!');
}
}
a.say();
(3)构造函数
function Person(){
}
new Person();
(4)绑定事件函数
btn.onclick = function(){}; //点击了这个按钮后才可以调用这个函数
(5)定时器函数
setInterval(function(){},1000); //这个函数是定时器自动1秒钟调用1次
(6)立即执行函数
(function(){
console.log('Hello!');
})() //立即执行函数是自动调用
this
1.函数内this的指向
- this的指向,是调用函数的时候确定的。调用方式的不同决定了this的指向不同。(一般指向我们的调用者)
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象、原型对象里面的方法都指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
2.改变函数内部this指向
- JavaScript专门提供了一些函数方法帮我们更优雅的处理函数内部this的指向问题,常用的有bind()、call()、apply()三种方法。
(1)call方法
- call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this指向。
fun.call(thisArg,arg1,arg2,...)
- thisArg:在fun函数运行时指定的this值。
- agr1,age2,…:传递的值。
- call()的主要作用就是可以实现继承。
function Father(uname,age){
this.uname = uname;
this.age = age;
}
function Son(uname,age){
Father(this,uname,age);
}
var son = new Son('andy',22);
console.log(son);
(2)apply方法
- apply()方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。
fun.apply(thisArg,[agrsArray])
- thisArg:在fun函数运行时指定的this值。
- argsArray:传递的值,必须包含在数组里面(但函数内获取的数值是去掉数值后的字符串或数值型等类型,而不是一个数组)。
- 返回值就是函数的返回值,因为它就是调用函数。
var a = {
name:'andy';
}
function fn(arr){
console.log(this);
console.log(arr); //输出结果为:'Hello'
}
fn.apply(a,['Hello']);
- apply()主要应用,比如:利用apply借助于数学内置对象求数组的最大值。
var arr = [1,24,45,14,89];
var max = Math.max.apply(Math,arr); //this的指向如果不改变,最好不要写null,因为可能会出现一些问题,最好是指向函数的调用者Math
console.log(max);
(3)bind方法
- bind()方法不会调用函数,但是能改变函数内部this的指向。
fun.bind(thisArg,arg1,arg2,...)
- thisArg:在fun函数运行时指定的this值。
- arg1,arg2:传递的其他参数。
- 返回由指定的this值和初始化参数改造的原函数拷贝。
var a = {
name: 'andy';
};
function fn(a,b){
console.log(this);
console.log(a + b);
};
var f = fn.bind(a,1,2); //返回的是原函数改变this之后产生的新函数
f();
- 应用举例:
<button></button>
<script>
var btn = document.querySelector('button');
btn.onclick = function(){
this.disabled = true; //这个this指向btn这个按钮
setTimeout(function(){
this.disabled = false; //这个this指向的是window,所以程序存在问题
},3000)
}
//解决方法1:
var btn = document.querySelector('button');
btn.onclick = function(){
this.disabled = true; //这个this指向btn这个按钮
var that = this;
setTimeout(function(){
that.disabled = false;
},3000)
}
//解决方法2:
var btn = document.querySelector('button');
btn.onclick = function(){
this.disabled = true; //这个this指向btn这个按钮
//var that = this;
setTimeout(function(){
this.disabled = false; //现在这个this就指向btn这个按钮
}.bind(this),3000)
}
</script>
<button></button>
<button></button>
<button></button>
<script>
var btns = document.querySelectorAll('button');
for(var i = 0;i < btns.length; i++){
btns[i].onclick = function(){
this.disabled = true;
setTimeout(function(){
btns[i].disabled = fasle; //因为for循环是同步任务,会立即执行,而定时器是异步任务,所以此时的i一定是4。因为btn[4]没有这个按钮,所以会报错
},2000)
}
}
//解决方法:
var btns = document.querySelectorAll('button');
for(var i = 0;i < btns.length; i++){
btns[i].onclick = function(){
this.disabled = true;
setTimeout(function(){
this.disabled = fasle; //此时这个this就指向点击的那个btn按钮
}.bind(this),2000)
}
}
</script>
3.call apply bind 总结
-
相同点
都可以改变函数内部的this指向。
-
区别点:
(1)call和apply会调用函数,并且改变函数内部this指向
(2)call和apply传递的参数不一样,call传递参数arg1,arg2…形式,apply必须是数组形式[argsArray]
(3)bind不会调用函数,但可以改变函数内部this指向
-
主要应用场景:
(1)call 经常做继承
(2)apply 经常跟数组有关系。比如:借助于数学对象实现数组最大值、最小值
(3)bind 不调用函数,但改变this指向。比如:改变定时器内部的this指向
严格模式
1.什么是严格模式
-
JavaScript除了提供正常模式(普通模式)外,还提供了严格模式。ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行JS代码。
-
严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
-
严格模式对正常的JavaScript语义做了一些更改:
(1)消除了JavaScript语法的一些不合理、不严谨之处,减少了一些怪异行为。(比如:变量可以不声明就直接赋值)
(2)消除代码运行的一些不安全之处,保证代码运行的安全。
(3)提高编译器效率,增加运行速度。
(4)禁用了在ECMSSscript的未来版本中可能会定义的一些语法,为未来版本的JavaScript做好铺垫。比如:一些保留字,如:class、enum、export、extends、import、super不能做变量名。
2.开启严格模式
- 严格模式可以应用到整个脚步或个别函数中。因此在使用时,可以严格模式分为为脚步开启严格模式和为函数开启严格模式两种情况。
(1)为脚步开启严格模式
- 为整个脚步文件开启严格模式,需要在所有语句之前放一个特定的"use strict";(或’user strict’;)。
<script>
"use strict";
console.log("这是严格模式!");
</srcipt>
- 因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。
- 有的script脚本是严格模式,有的script脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建了一个作用域而不影响其他script脚本文件。
<script>
(function(){
"use strict";
var num = 10;
function fn(){}
})();
</script>
(2)为函数开启严格模式
- 给某个函数开启严格模式,需要把"use strict";(或’use strict’;)声明放在函数体所有语句之前。
<script>
function(){
'use strict'; //下面的代码按照严格模式执行
}
function(){
//下面的代码按照普通模式执行
}
</script>
3.严格模式中的变化
- 严格模式对JavaScript的语法和行为,都做了一些改变。
(1)变量规定
① 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用。
② 严禁删除已经声明变量。例如:delete x;语法是错误的。
(2)严格模式下this的指向问题
① 以前在全局作用域函数中的this指向window对象。
② 严格模式下全局作用域中函数中的this是undefined。
③ 以前构造函数不加new也可以调用,当普通函数,this指向window全局对象。
function Person(){
this.uname = 'andy'; //此时的this指向window
}
Person();
console.log(window.uname);
④ 严格模式下,如果构造函数不加new调用,this会报错。(因为此时的this指向undefined,不能给undefined添加属性或方法)
"use strict";
function Person(){
this.uname = 'andy'; //此时的this指向undefined
}
Person(); //此时会报错
⑤ new实例化的构造函数,this指向创建的对象实例。
⑥ 定时器的this还是指向window。
⑦ 事件、对象的this还是指向调用者。
(3)函数变化
① 函数不能有重名的参数。
② 函数必须声明在顶层(即最外层,如下代码所示)。新版本的JavaScript会引入“块级作用域”(ES6中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数(即if判断语句或for循环语句等非函数的代码块内就不可以嵌套函数,而函数内嵌套函数就可以)。
function fn(){ //没有被包裹就是在顶层
}
高阶函数
- 高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
- 接收函数作为参数:
<script>
function fn(callback){
callback && callback();
}
fn(function(){ alert('Hello!')})
</script>
2.将函数作为返回值输出:
<script>
function fn(){
return function(){}
}
fn();
</script>
- 以上两种情况的fn都是高阶函数。
- 函数也是一种数据类型,同样可以作为参数,传递给另外一个函数使用。最典型的就是作为回调函数。
- 将函数作为返回值输出最典型的应用就是闭包函数。
闭包
1.变量作用域
-
变量根据作用域的不同分为两种:全局变量和局部变量。
(1)函数内部可以使用全局变量。
(2)函数外部不可以使用局部变量。
(3)当函数执行完毕,本作用域的局部变量会销毁。
2.什么是闭包
-
闭包指有权访问另一个函数作用域中变量的函数。
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。
-
举例:
function fn(){
var num = 10;
function fun(){
console.log(num);
}
fun();
}
fn();
//fun这个函数的函数作用域访问了另外一个函数fn里面的局部变量num,所以有闭包的产生
function fn(){
var num = 10;
return function(){
console.log(num);
}
}
var f = fn();
f();
-
闭包的主要作用:延伸了变量的作用范围,并且函数执行完毕后变量并不会销毁,而是等到其他作用域调用了该变量后才会销毁。
-
闭包案例:
(1)循环注册点击事件。(点击li输出当前li的索引号)
<ul class="nav"> <li>a</li> <li>b</li> <li>c</li> <li>d</li> </ul> <script> var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0; i < lis.length; i++){ lis[i].onclick = function(){ console.log(i); //因为for循环是同步任务,而点击事件是异步任务,所以无论点击哪个li,输出结果都是4 } } //解决方法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(利用闭包的方式得到当前li的索引号): var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0; i < lis.length; i++){ (function(i){ lis[i].onclick = function(){ console.log(i); } })(i); } //立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i这个变量 </script>
(2)循环中的setTImeout()(3秒钟之后,打印所有li元素的内容)
<ul class="nav"> <li>a</li> <li>b</li> <li>c</li> <li>d</li> </ul> <script> var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0; i < lis.length; i++){ setTimeout(function(){ console.log(lis[i].innerHTML); //因为for循环是同步任务,而定时器是异步任务,所以一直都是4,因为没有lis[4],所以会报错 },3000) } //解决方法(利用闭包): var lis = document.querySelector('.nav').querySelectorAll('li'); for(var i = 0; i < lis.length; i++){ (function(i){ setTimeout(function(){ console.log(lis[i].innerHTML); },3000) })(i); } </script>
-
思考题:
var name = "andy"; var object = { name: "Mary", getName: function(){ return function(){ return this.name; }; } }; console.log(object.getName()()); //因为function(){}()类似立即执行函数,所以里面的this指向window,所以返回的是"andy",没有闭包的产生
var name = "andy"; var object = { name: "Mary", getName: function(){ var that = this; return function(){ return that.name; }; } }; console.log(object.getName()()); //因为var that = this;且此时的this指向object这个对象,所以that也指向object这个对象,所以最后的输出结果为:"Mary",并且有闭包的产生
递归
1.什么是递归
-
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解就是,函数内部自己调用自己,这个函数就是递归函数。
-
递归函数的作用和循环效果一样。
-
跟循环会出现死循环的现象一样,递归也很容易发生“栈溢出”错误,所以必须添加退出条件return。
var num = 1;
function fn(){
console.log(1);
if(num == 4){
return; //退出条件
}
num++;
fn();
}
fn();
-
利用递归求数学题:
(1)利用递归求1~n的阶乘
function fn(n){ if(n == 1){ return 1; } return n * fn(n-1); //fn(n-1)还没有算出来时,不可以返回(return) } console.log(fn(3));
-
利用递归遍历数据:
(1)输入id号,就可以返回数据对象
var data = [{ id: 1, name: '家电', goods: [{ id: 11, gname: '冰箱' },{ id: 12, gname: '洗衣机' }] },{ id: 2, name: '服饰' }]; function getID(json,id){ var 0 ={}; 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,11));
2.浅拷贝和深拷贝
-
浅拷贝只是拷贝一层,更深层次对象级别的,只拷贝引用(即地址)。
-
深拷贝会拷贝多层,每一级别的数据都会拷贝。
-
浅拷贝原生js实现方式:
var obj = {
id: 1,
name: 'andy',
msg: {
age: 22
}
};
var a = {};
for(var k in obj){
o[k] = obj[k];
}
console.log(a); //复杂数据类型只是拷贝了地址。
- Object.assign(target,…sources) ES6新增的浅拷贝方法。
Object.assign(a,obj);
- 深拷贝原生js实现方式:
var obj = {
id: 1,
name: 'andy',
msg: {
age: 22
},
color: ['blue','red']
};
var a = {}
function deepCopy(newobj,oldobj){
for(var k in oldobj){
var item = oldobj[k];
//判断属性值是否是数组
if(item instanceof Array){ //因为数组也属于对象,所以判断是否属于数组应该写在判断是否属于对象之前
newobj[k] = [];
deepCopy(newobj[k],item);
}else if(item instanceof object){
//判断属性值是否是对象
newobj[k] = {};
deepCopy(newobj[k],item);
}else {
//属性值属于简单数据类型
newobj[k] = item;
}
}
}
deepCopy(a,obj);
微信公众号也会定期更新,觉得文章写得还可以的,可以加个关注!点个赞!谢谢!