IIFE(immediately invoked Function expression) 立即调用函数表达式,它是一种特殊的表达式,通过该表达式可以在定义函数的同时来执行函数。
语法:
;(function(形参){
//函数体
})(实参)
示例:
;(function(){
console.log(1)
})()
注意前面的分号并不是必须的,由于LIFE是一句表达式,所以添加分号是为了和其他的表达式区分开。例如:
(function(){
console.log('立即执行函数1');
})()
(function(){
console.log('立即执行函数2');
})()
案例:LIFE+闭包完成点赞计数器
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<ul>
<li><input value="大拇指1" type="button"></li>
<li><input value="大拇指2" type="button"></li>
<li><input value="大拇指3" type="button"></li>
<li><input value="大拇指4" type="button"></li>
<li><input value="大拇指5" type="button"></li>
</ul>
<script>
let buttons=document.getElementsByTagName("input");
for(let i=0;i<buttons.length;i++){
buttons[i].onclick=(function(){
let count=0;
return function(){
count++;
console.log(count);
}
})()
}
</script>
</body>
</html>
递归就是在函数体内调用本函数。
例如:
function a(){
a();
}
a();
但是这样执行函数的结果只会有一个:
这种异常时因为超出栈内存的最大容量造成的,因为每次执行都会产生上下文对象并入栈。此处的a函数会一直调用自身,最终导致栈内存溢出,这种情况是死递归,是一定要避免的一种情况。
使用递归一定要把握住核心,递归是两个动作。递和归。递是指调用函数本身,归是指停止调用,在一个递归函数中,一定会有一个if-else结构,用于书写递的代码和归的代码。
案例1:求1-100的和
function sum(n){
if(n===1){
return 1;
}else return n+sum(n-1);
}
console.log(sum(100));
案例2:阶乘
function factorial(n){
return n==1?n:n*factorial(--n);
}
console.log('5的阶乘:',factorial(5));
案例3:斐波那契数列
function fabonic(n){
if(n==1||n==2)
{
return 1;
}else{
return fabonic(n-1)+fabonic(n-2);
}
}
console.log(fabonic(8));
for in可以用于遍历数组,但是主要是用于遍历对象属性
let student={
name:"张三",
age:19,
f:function(){
console.log(1);
}
}
for(let p in student){
console.log(p);
}
for in循环遍历的是对象的属性,并非属性的值,如果要遍历对象的值,需要通过对象[属性名]
来进行访问。
let student={
name:"张三",
age:19,
f:function(){
console.log(1);
}
}
for(let p in student){
console.log(student[p]);
}
如果我们只想遍历属性,不想遍历函数可以通过instanceof关键字来判断数据的真实类型
let student={
name:"张三",
age:19,
f:function(){
console.log(1);
}
}
for(let p in student){
console.log(student[p] instanceof Function);
}
补充:typeof函数也可以用于判断数据类型,但是typeof不能区分数组和对象,数组和对象通过typeof函数得到的结果都是object。
for of是es6提出的用于快捷遍历数组的方式。
let arr = [10, 20, 30];
for(let item of arr) {
console.log(item);
}
直接复制地址,只有这种方式是绝对的浅拷贝。
let student={
name:'Giles',
child:{
name:'apple'
}
}
let targetObj=student;
console.log(student);
console.log(targetObj);
将对象中的属性值复制到另外一个对象的相同属性中,两者不是同一个值,包括对象的子对象也需要深度拷贝,才算是绝对的深度拷贝。
var source={name:'Giles',age:38,scores:[10,20,30]};
var target=JSON.parse(JSON.stringify(source));
其他拷贝方式:
扩展运算符
var source={name:'Giles',age:38};
var target={...source};
console.log(source);
console.log(target);
Object.assign方式
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。注意:Object.assign()拷贝的是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值,来看个例子:
var source={name:'Giles',age:38};
var target={};
Object.assign(target,source);
console.log(target);
同步和异步的概念:
同步:指的是在同一个时间段内,只能做一件事情,必须等到上一件事情完成后,才能进行下一件事情;
异步:指的是在同一个时间段内,可能同时处理多件事情,也就意味着上一件事情没有完成,并不影响下一件事情的进行;
大家都知道ajax是异步的,也就是说请求发出以后,其他的代码会继续执行不会等待ajax的结果回来再执行。在某些场景中,这种异步的方式反而会给我们带来编程上的困扰。例如:电影项目中根据电影信息查询电影类型信息。
当我们的业务需要两个以上的请求才能完成,这时为了保证我们的DOM操作是在两次请求都完成以后去执行的,我们会将第二个Ajax请求嵌入到第一个Ajax请求的回调中。如果我们的业务过于复杂,这种嵌套的层级关系就会很复杂,这就是回调地狱。
let promise1 = new Promise(function(success, fail) {
$.ajax({
url: "../data/students.json",
success: function(data) {
success(data);
}
});
});
let promise2 = promise1.then(function(data) {
console.log(data);
return new Promise(function(success, fail) {
$.ajax({
url: "../data/classs.json",
success: function(data) {
success(data);
}
});
});
});
promise2.then(function(data){
console.log(data);
});
async和await是es7中引入的新特性,其目的是解决异步编程中Promise的一些不便之处,例如:Promise的then式调用代码结构看起来不是很清晰,比较难懂,还有Promise中第二个异步函数中要获取第一个异步函数中的数据,只能以组装的方式传递下去,对于参数的处理比较麻烦。这些问题都可以通过await和async来解决,这也是异步编程的最优解决方案。
await的作用是等待一个Promise对象中的异步代码执行结束,并返回success中的数据。但是await必须和async一起使用。
async的作用是定义一个函数,在该函数中可以使用await关键字执行一个Promise的异步函数。
function selectMovies() {
return new Promise(function (resovle, reject) {
$.ajax({
url: "https://www.fastmock.site/mock/bb4157f45a0b5ffdcb3f6d984517a6c0/woniuMovie/getAllMovies",
dataType: "json",
success: function (data) {
resovle(data);
}
});
});
}
//定义函数返回第二个Primise
function selectTypes() {
return new Promise(function (resovle, reject) {
$.ajax({
url: "https://www.fastmock.site/mock/bb4157f45a0b5ffdcb3f6d984517a6c0/woniuMovie/getAllTypes",
dataType: "json",
success: function (data) {
resovle(data);
}
});
});
}
//通过一个异步函数来执行2个Promise请求
async function selectMovieDetail(){
let movies=await selectMovies();
console.log(movies);
let types=await selectTypes();
console.log(types);
console.log("数据渲染");
}
selectMovieDetail();
function ajax({url,type="get",data,dataType="json"}){
return new Promise(resolve=>{
$.ajax({
url:url,
type:type,
data:data,
dataType:dataType,
success:function(data){
resolve(data)
}
})
});
}
function get1(){
return ajax({url:"data/student.json"});
}
function get2(){
return ajax({url:"data/class.json"});
}
async function selectMovie(){
let data=await get1();
console.log(1);
console.log(data);
let data1=await get2();
console.log(2);
console.log(data1);
}
selectMovie();
函数防抖的作用是在一段时间内频繁触发事件,我们只执行最后一次。例如:在一个输入框中输入商品名称,然后通过商品名称去服务器查询商品,如果直接使用input事件,在频繁输入的情况下会查询多次,但是其中的多次查询是毫无意义的,因为从用户使用习惯上来讲,用户既然在持续输入就说明还没有输入完毕,此时去查询的数据,纯属浪费资源。
函数防抖的思路是将真正要执行的搜索代码写到setTimeout函数中,通过setTimeout函数来延迟执行,在setTimeout执行之前的这段时间,如果又触发了事件,那么我们就将前面的计时器清除,并重新添加setTimeout计时器。
实现如下:
let time=null
document.getElementById("inp").oninput=function(){
clearTimeout(time);
time=setTimeout(function(){
console.log("从服务器查询"+document.getElementById("inp").value+"的数据");
},500);
}
函数节流的作用是在一段时间内频繁触发事件,我们只执行第一次。例如:滚动条加载数据的功能中,一旦滚动触发,我们通常会通过AJAX请求去从服务器查询数据,但是AJAX的请求是需要一段时间才能成功拿到数据的。那么在这段时间内,如果又继续进行滚动,又去查询数据是没有意义的,因为用户连第一波数据都没看到,下一波数据展示给用户干啥呢?所以,当第一次AJAX请求在执行过程中,再次触发的滚动不应该去发送新的请求。
函数节流的实现方式
(1)标记法
标记法的思路就是通过一个变量来记录AJAX的执行状态,如果AJAX正在执行,那么再次触发的事件我们将不予执行。
let b=false;
window.onscroll=function(){
if(!b){
b=true;
setTimeout(function(){
console.log(1);
b=false;
},1000);
}
}
(2)时间戳法
时间戳法的思路是时间触发时,先判断当前时间和上次执行的时间间隔是否大于我们设定的间隔,如果大于则执行代码并更新上次执行时间。
let last=0;
window.onscroll=function(){
let current=new Date().getTime();
if(current-last>1000){
console.log(1);
last=current;
}
}