一、函数定义和调用
1.函数的定义方式
方式一
函数声明方式 function 关键字(命名函数)
function fn(){}
方式二
函数表达式(匿名函数)
var fn = function(){}
方式三
new Function()
var f = new Function('a','b','console.log(a + b);');//语法
var fn = new Function('参数1','参数2',....,'函数体');
注意:
Function里面参数都必须是字符串格式
第三种方式执行效率很低,也不方便书写,因此较少使用
所有函数都是Function的实例对象
函数也属于对象
2.函数的调用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>01函数的定义和调用方式</title>
</head>
<body><script>//1.自定义函数(命名函数)function fn(){}//2.函数表达式(匿名函数)var fun = function(){}//3. 利用new Function(('参数1','参数2',....,'函数体');var f = new Function('a','b','console.log(a + b);');f(1,2);//4. 所以的函数都是Function的实例(对象)console.dir(f);//5.函数也属于对象console.log(f instanceof Object);</script>
</body>
</html>
函数的调用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>02函数的调用</title>
</head>
<body><script>//1.普通函数function fn(){console.log('人生的巅峰');}fn();fn.call();//2.对象的方法var o = {sayHi : function(){console.log('人生的巅峰2');}}o.sayHi(); //对象.函数();//3.构造函数function Star(){}new Star();//4.绑定事件的函数//btn.onclick = function(){} //点击了按钮就可以调用这个函数//5.定时器函数setInterval(function(){},1000);//6.立即执行函数(function(){console.log('hello');})();</script>
</body>
</html>
二、this
1.函数内部的this指向
这些this的指向,是当我们调用函数的时候确定的,调用方式不同决定了this的指向不同。
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象,原型对象里面的方法也指向了实例对象 |
对象方法调用 | 该方法所属的对象 |
事件绑定方法 | 绑定事件的对象 |
定时器函数 | window |
立即执行函数 | window |
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>03this的指向</title>
</head>
<body><button>点击</button><script>//函数的不同调用方式决定了this的指向不同 //1.普通函数this指向window对象function fn(){console.log('普通函数的this--->' + this);}window.fn();//2.对象的方法,this指向的是对象ovar o = {sayHi : function(){console.log('对象方法的this--->' + this);}}o.sayHi(); //对象.函数();//3.构造函数function Star(){}Star.prototype.sing = function(){console.log('构造的this--->' + this);}var ldh = new Star();ldh.sing();//4.绑定事件函数this指向的函数调用者 (比如:btn.onclick 函数中的this指向的btn对象)var btn = document.querySelector('button');btn.onclick = function(){console.log('绑定事件的函数中的this--->' + this);}//5.定时器函数this,指向的就是windowwindow.setTimeout(function(){console.log('定时器函数中的this--->' + this);},1000);//6.立即执行函数 this还是指向window(function(){console.log('立即执行函数this--->' + this);})();</script>
</body>
</html>
2.改变函数内部this指向
2.1 call方法
call() 方法调用一个对象,简单的理解调用函数的方式,但是它可以改变函数this指向
应用场景:经常做继承
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>04改变函数内this指向call方法</title>
</head>
<body><script>//js 中一共是提供了三种方法可以改变this的指向:call() apply() bind()var obj = {name: 'amdin'}function fn(a,b){console.log(this);console.log(a + b);}//1.call() 改变函数this的指向fn.call(obj,1,2);function Father(name,age,sex){this.name = name;this.age = age;this.sex = sex;}function Son(name,age ,sex){//this:指向SonFather.call(this,name,age,sex);}var son = new Son('刘德华',80,'男');</script>
</body>
</html>
2.2 apply方法
apply() 方法调用一个函数。简单的理解为调用函数的方式,但是它可以改变函数的this指向
应用场景:经常跟数组有关系
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>05.改变函数内this指向apply方法</title>
</head>
<body><script>//js 中一共是提供了三种方法可以改变this的指向:call() apply() bind()//2.apply() 方法 应用/运用var obj = {name: 'admin'}function fn(arr){console.log(this);console.log(arr);}fn.apply(obj,['pink','red']);fn(['pink','red'])//1.也是调用函数,第二个可以改变函数内部的this指向//2.但是它的参数必须是数组(伪数组)//3.apply的主要应用比如我们可以利用apply借助于数学内置对象求数组最大值//Max.max();var arr = [1,66,3,99,4];var arr1 = ['red','pink']// var max = Math.max.apply(null,arr);// var min = Math.min.apply(null,arr);var max = Math.max.apply(Math,arr);var min = Math.min.apply(Math,arr);console.log(max);console.log(min);</script>
</body>
</html>
2.3 bind 方法
bind()方法不会调用函数,但是能改变函数内部this指向,返回的是原函数改变this之后产生的新函数。
如果指向改变this指向,并且不想要调用这个函数的时候,就可以用bind
**应用场景:**不调用用户,但是还想改变this指向
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>06.05.改变函数内this指向bind方法</title>
</head>
<body><button>点击</button><button>点击</button><button>点击</button><script>//js 中一共是提供了三种方法可以改变this的指向:call() apply() bind()//2. bind() 意思就是捆绑或绑定var obj = {name:'admin'}function fn(a,b){console.log(this);console.log(a + b);}//fn(1,2);var f = fn.bind(obj,1,2);f();//1)不会调用原来的函数,可以改变原来函数内部的this指向//2)返回的是原函数改变this之后产生的新函数//3)如果有函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时我们可以bind//4)我们有一个按钮,当我们点击之后,就想用禁用这个按钮,3秒之后开启这个按钮// var btn1 = document.querySelector('button');// btn1.onclick = function(){// this.disabled = true;// //设置定时任务// setTimeout(function(){// this.disabled = false; //定时器函数中this指向的是window对象// }.bind(this),3000); //因为这里的this是写在定时器之外,所以这里的this还是btn这个对象// }var btns = document.querySelectorAll('button');//给每个按钮注册点击事件for(var i = 0; i < btns.length; i++){btns[i].onclick = function(){this.disabled = true;//3秒后按钮重新可以被点击setTimeout(function(){this.disabled = false;}.bind(this),3000);}}</script>
</body>
</html>
2.4 call、apply、bind三者异同
- 共同点
- 都可以改变this的指向
- 不同点
- call和apply会调用函数,并且改变函数内部的this指向
- call和apply传参不一样,call传递参会使用逗号分隔,apply使用数组传参
- bind必会调用函数,可以改变函数内部的this指向
- 应用场景
- call经常做继承
- apply经常跟数组有关系,比如:借助数学对象实现数组最大值和最小值
- bind不调用函数,但是还想改变this的指向,比如:改变定时器内部的this指向
三、严格模式
官网资料地址:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
1.什么是严格模式
Js除了提供正常的模式之外,还提供了严格模式(strict mode)。
ES5的严格模式采用具有限制性js变体的一种方式——即在严格的条件下运行js代码。
严格模式在IE10以上版本浏览器中才会被执行,旧版本浏览器中会被忽略。
严格模式对正常的js语义做了一些修改:
- 清除了js语法的一些不合理、不严谨之处、减少了一些怪异行为
- 清除了js代码运行的一些不安全之处,保证代码的运行安全。
- 提高了编译器的效率,增加运行速度
- 禁用了ECMAScript的未来本版中可能会定义的一些语法,为未来新版本的JS 做了铺垫。比如:一些保留关键字
class、enum、export、extends、import、super不能做为变量
2.开启严格模式
严格模式可以应用到整个脚本或者是个别函数中,因此在使用的时候,我们可以将yan模式分为脚本开启严格模式还是函数开启严格模式两种情况。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>07.开启严格模式</title>
</head>
<body><!-- 为整个脚本(script标签)开启严格模式 --><script>'use strict';// 下面js代码就会按照严格模式执行代码</script><script>// 给某个函数开启严格模式function fn(){'use strict';// 下面js代码就会按照严格模式执行代码}function fn(){'use strict';// 下面js代码就会按照严格模式执行代码}</script><script>(function(){'use strict';// 下面js代码就会按照严格模式执行代码})();</script>
</body>
</html>
2.1 脚本开启严格模式
有的script脚本是严格模式,有的script是正常模式,这样不利于文件的合并,所以可以将整个脚本文件放在一个立即执行函的匿名函数里面,这样独立创建有一个作用域而不影响其它的script脚本文件。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>08.开启严格模式后的变化</title>
</head>
<body><script>'use strict'; //use strict:严格的//1.我们的变量名必须先声明后使用//num = 10; //在严格模式下,变量必须用var声明后才能使用// console.log(num);var num = 10;console.log(num);//2.我们不能随意删除已经声明好变量//delete num; //删除变量num//console.log(num);//3.严格模式下全局作用域中函数的this 是undefinedfunction fn(){console.log(this); //undefined}fn();//4.在严格模式下,如果构造函数不加new调用,this指向的是undefined,如果给它赋值则会报错function Star(){this.sex = '男'}//Star();var ldh = new Star();console.log(ldh.sex);//5.定时器this指向还是windowsetTimeout(function(){console.log(this);},2000);//6.在严格模式下函数里面的参数不允许重名var a = 1;a = 2;// function fn(a,a){ //参数名不允许重复// console.log(a);// }// fn();</script>
</body>
</html>
2.2 为函数开启严格模式
要给某个函数开启严格模式,需要
'use strict';
写到函数体所有语句之前
function fn(){'use strict';//....
}//表示当前fn函数开启了严格模式
四、高阶函数
高阶函数:是对其它函数进行操作的函数,它接受函数作为参数或者将函数作为返回值输出。
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>09高阶函数</title><style>div{position: absolute;width: 100px;height: 100px;background-color: pink;}</style><script src="animate.js"></script>
</head>
<body><div></div><script>//高阶函数——函数可以作为参数传递function fn(a,b,callback){console.log(a + b);callback && callback();}fn(1,2,function(){console.log('我是最后调用的');})</script>
</body>
</html>
五、闭包
1.变量的作用域【复习】
变量根据作用域的不同分为:全局变量和局部变量
- 函数内部可以使用全局变量
- 函数外部不可以使用局部变量
- 当函数执行完毕后,本作用域内局部变量会被销毁
2.什么是闭包
closure:闭包。指有权访问另一个函数作用域中变量的函数。
简单的理解:一个作用域可以访问另一个函数内部的局部变量
<!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>//闭包:指有权访问另一个函数作用域中的变量的函数//闭包:我们fun这个函数的作用域,访问了另一个函数fn里面的局部变量numfunction fn(){var num = 10;function fun(){console.log(num);}fun();}fn();</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>11.闭包的作用</title>
</head>
<body><script>//闭包:指有权访问另一个函数作用域中的变量的函数//一个作用域可以访问另一个函数的局部变量//fn外面的作用域可以访问fn内部的局部变量//闭包的主要作用:延申了变量的作用范围function fn(){var num = 10;return function(){console.log(num);};}var f = fn();f();</script>
</body>
</html>
4.闭包案例
4.1 得到li当前的所以那红
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>12.闭包应用-点击li输出索引号</title>
</head>
<body><ul class="nav"><li>星期一</li><li>星期二</li><li>星期三</li><li>星期四</li></ul><script>//闭包//1.我们可以利用动态添加属性的方式给每个li添加索引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(i); //表示i是循环中的i// //console.log(this.index);// }// }//2.利用闭包的方式得到每个当前小li的索引号for(var i = 0; i < lis.length; i++){//利用for循环创建了4个立即执行函数//立即执行哈桑农户也为小闭包//立即执行函数为小闭包因为立即执行函数里卖弄的任何一个函数都可以使用它的i这个变量(function(i){lis[i].onclick = function(){console.log(i);}})(i);}</script>
</body>
</html>
4.2 闭包定时器中的应用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>13.闭包应用-定时器</title>
</head>
<body><ul class="nav"><li>星期一</li><li>星期二</li><li>星期三</li><li>星期四</li></ul><script>// 闭包应用- 3秒钟之后,打印所有li元素的内容var lis = document.querySelector('.nav').querySelectorAll('li');for(var i = 0; i < lis.length; i++){//生成了4个立即执行函数(function(i){setTimeout(function(){console.log(lis[i].innerHTML);},3000);})(i);}</script>
</body>
</html>
4.3 打车价格案例
需求:计算打车价格
//1.打车的起步价13(3公里以内),之后每多一公里增加5块钱,用户输入公里数就可以计算打车的价格
//2.如果有拥堵的情况,总价格多收取10块钱拥堵费
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>14.计算打车的价格</title>
</head>
<body><script>//需求:计算打车价格//1.打车的起步价13(3公里以内),之后每多一公里增加5块钱,用户输入公里数就可以计算打车的价格//2.如果有拥堵的情况,总价格多收取10块钱拥堵费var car = (function(){var start = 13; //起步价,局部变量var total = 0; //总价,局部变量// 返回对象return {//正常的价格price: function(n){ //n是用户打车的距离if(n <= 3){total = start;//小于或等于3公里,就是起步价} else {total = start + (n - 3) * 5;}return total;},//拥堵之后的费用yd:function(flag){ //flag是是否拥堵return flag? total + 10:total;}}})();var total1 = car.price(5);console.log(total1); //23var total2 = car.yd(true);console.log(total2); // 33var total3 = car.price(1); //13var total4 = car.yd(false); //13console.log(total3, total4);</script>
</body>
</html>
六、递归
1.什么是递归
递归:如果一个函数再内部可以调用其本身,那么这个函数他就是递归函数。
简单理解:函数内部自己调用了自己,这个函数就是递归函数
**注意:**递归函数的作用和循环效果一样,由于递归很容易出现(栈移除),所以要加退出的条件
2.求1~n的阶乘
3! = 1 × 2 × 3
5!= 1 × 2 × 3 × 4 × 5
n! = 1 × 2 × … n
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>15.求n的阶乘</title>
</head>
<body><script>//利用递归函数求n的阶乘function fn(n){if(n == 1){ //结束条件return 1;}return n * fn(n - 1);}//n=1 fn(1) = 1;//n=2 fn(2) = 2 * f(1) = 2 * 1;//n=3 fn(3) = 3 * fn(3-1) = 3 * 2 * 1;//n=4 fn(4) = 4 * fn(4-1) = 4 * 3 * 2 * 1;//....console.log(fn(4));console.log(fn(10));</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>16.斐波拉契数列</title>
</head>
<body><script>//斐波拉契数列: 1 1 2 3 5 8 13 21 34 55....function fb(n){if(n == 1 || n == 2){ //结束条件return 1;}//数的值是前两个数的和return fb(n - 1) + fb(n -2);}//fb(1) = 1//fb(2) = 1//fb(3) = fb(3-1) + fb(3-2) = fb(2) + fb(1) = 1 + 1 = 2//fb(4) = fb(4-1) + fb(4-2) = fb(3) + fb(2) = 2 + 1 = 3//fb(5) = fb(5-1) + fb(5-2) = fb(4) + fb(3) = 3 + 2 = 5//fb(6) = fb(6-1) + fb(6-2) = fb(5) + fb(4) = 5 + 3 = 8//...console.log(fb(10));</script>
</body>
</html>
4.利用递归遍历数据
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>17.利用递归遍历数据</title>
</head>
<body><script>var data = [{id:1,name:'家电',goods:[{id:11,gname:'冰箱',goods:[{id:111,name:'海尔'},{id:222,name:'美的'}]},{id:22,name:'洗衣机',goods:[{id:111,name:'小天鹅'},{id:222,name:'美的'}]}]}];//1.利用foreach遍历里面的每一个对象function getID(json,id){var obj = {}json.forEach(function(item){//console.log(item); //2个数组元素if(item.id == id){ //如果对象的id == 传入的id//console.log(item);obj = item; //将一级分类对象给到自定义对象obj} else if(item.goods && item.goods.length > 0){ //我们想要得到数据11 和 12 可以利用递归函数//里面应该有goods这个数组并且数组长度部位0obj = getID(item.goods,id)} });return obj;}console.log(getID(data,1));console.log(getID(data,11));console.log(getID(data,111));</script>
</body>
</html>
七、浅拷贝和深拷贝
1.浅拷贝
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>18.浅拷贝</title>
</head>
<body><script>//浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用var obj = {id:1,name:'admin',msg:{age:18}}var o = {};for(var k in obj){//k 是属性名 obj[k] 该属性的值o[k] = obj[k]; //设置对象o属性k的值为obj[k]}// o.msg.age = 20;// console.log(o);// console.log(obj);console.log('---------------------------');//Object.assign(o,obj);o.msg.age = 21;console.log(o);console.log(obj);console.log(o === obj);</script>
</body>
</html>
2.深拷贝
深拷贝拷贝多层,每一级的数据都会拷贝
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>19.深拷贝</title>
</head>
<body><script>//深拷贝拷贝多层,每一级的数据都会拷贝var obj = {id:1,name:'admin',msg:{age:18}}var o = {};//封装函数function deepCopy(newObj,oldObj){for(var k in oldObj){//1.判断我们的属性值属于哪种数据类型//获取属性值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(o,obj);o.msg.age = 20;console.log(o);console.log(obj);</script>
</body>
</html>