执行上下文【代码的执行环境】
-
全局执行上下文
-
函数执行上下文
-
eval执行上下文[很少使用,了解即可]
var i="3*3+5" document.write(eval(i))//14
执行上下文调用栈【代码的执行流程】【先进后出】
-
全局上下文入栈
-
函数1上下文入栈
-
函数2上下文入栈
-
函数N上下文入栈
-
函数N上下文出栈
-
函数2上下文出栈
-
函数1上下文出栈
-
全局上下文出栈
递归
含义:
-
函数内部调用自身的过程
特点:
-
函数调用函数,执行传递的动作
-
确定最后一次的终止条件,执行回归的动作
1、递归面试题1[用递归求阶乘]
function jie(num){
//确认边界,回归
if(num==1){return 1};
//传递
var j=jie(num-1)
return n*j;//jie(num-1)*j
}
剖析图
2、递归面试题二【快速排序】
思路分析:
-
在数据集之中,选择一个元素作为”基准”
-
所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。
-
对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。 案例模拟: 4,7,3,20,11,2 基准:[3,2],4,[7,20,11] [3,2] 基准:2,[3] [3] [7,20,11] 基准:7,[20,11] [20,11] 基准:11,20 代码实现:
let arr=[4,7,3,20,11,2]; function quickSort(list){ if(list.length==0){ return list; } //1.找基准值,就是第一个值 let basic = list[0]; let left_arr=[]; let right_arr=[]; if(list.length==1){ return basic; } //2.遍历数组,从基准值的下一个开始 for(let i=1;i<list.length;i++){ if(basic<list[i]){ //3.放右边 right_arr.push(list[i]) } if(basic>list[i]){ //4.放左边 left_arr.push(list[i]) } } return [].concat(quickSort(left_arr),basic,quickSort(right_arr)); } console.log(quickSort(arr));
(3)、补充:二分查找法(查找数组指定数字的下标)
前提:有序数组
思路:
-
利用循环
-
计算出中间值,然后和要查找的数字比较是在中间值的左侧还是左侧
-
缩短查找范围,再重新计算中间值,继续判断
-
直到要查到的数字就是中间值为止
代码
<script>
//利用二分法,查找指定数字在数组中的下标
function binarySearch(arr, num) {
//1、找出中间值
var start = 0;
var stop = arr.length - 1;
var middle = parseInt((start + stop) / 2);
//2、比较中间值和要查找的数字
while (arr[middle] != num && stop > start){
if (arr[middle] > num) {
//说明我要找的值在左边
stop = middle - 1;
} else {
//说明我要找的值在右边
start = middle + 1;
}
//3、每次循环都要重新计算中间值
middle = parseInt((stop + start) / 2)
}
//如果搜不到这个值的话,返回-1.否则返回索引号(下标)
return arr[middle] != num ? -1 : middle;
}
var m = binarySearch([1, 2, 3, 4, 5, 6], 4)
console.log(m);
</script>
对象浅拷贝
概念:复制出来的对象做修改时对原对象造成了影响 写法:
-
引用赋值:var 对象B=对象A;
-
扩展运算符:var 对象B={…对象A}
-
展开符只能展开一层,所以里面的复杂数据类型还是浅拷贝
-
-
Assign :Object.assign(目标对象,源对象);
-
里面的复杂数据类形是浅拷贝
-
-
复制了引用地址而已[浅拷贝]
<script>
//浅拷贝:复制出来的对象做修改时对原对象造成了影响
var a ={
name:'jack',age:30,work:['写代码','开滴滴','跑摩的']
}
var b = a; //复制了引用的地址而已【浅拷贝】
b.name='rose';
console.log(a.name);//rose
// 虽然c是新创建的一个对象,c不等于a,
//但是:展开符只能展开一层,但是a里面还有一个对象,就是work数组
var c = {...a};
c.name='peter'
//c和a是不同的两个对象,但是c和a他们俩共用同一个work
c.work[0]='项目经理'
console.log(a.work);//['项目经理','开滴滴','跑摩的']
var x ={};
var y={code:'XA003',list:[1,2,3]}
//将y的全部内容复制到x里【浅拷贝】
var result = Object.assign(x,y);
console.log(x);
x.list.push(4,5,6);
console.log(y);
</script>
对象深拷贝
概念:复制出来的对象和源对象无任何关系,不会再互相影响,在堆内存中独立存储 写法: 方式一: JSON.stringify(源对象)、JSON.parse(源对象JSON字符串) 缺点:undefined的值、函数无法拷贝
<script>
var phone={
brand:'MI',
title:'K30S',
price:2999,
date:null,
power:{
GPS:'查看定位信息',
call(){
alert('打电话')
},
play(){
alert('玩游戏')
}
}
}
var phone2 = {};
//深拷贝(函数无法拷贝、undefined的值无法拷贝)
var str = JSON.stringify(phone);
phone2 = JSON.parse(str)
phone2.title='MIX'
phone2.power.GPS='北斗定位';
console.log(phone2);
</script>
方式二: 递归深拷贝
function deepCopy(target,source){
for(let i in source){
if(typeof source[i]=='object'){
let p=null;
if(source[i].length){
//数组
p=[];
}else{
//对象
p={};
}
//1、拷贝方法,对象
target[i] = deepCopy(p,source[i])
}else{
//2、直接拷贝属性
target[i]=source[i];
}
}
return target;
}
<script>
//深拷贝的终极方案(递归深拷贝)
function deepCopy(target,source){
for(let i in source){
if(typeof source[i]=='object'){
let p=null;
if(source[i].length){
//数组
p=[];
}else{
//对象
p={};
}
target[i] = deepCopy(p,source[i])
}else{
target[i]=source[i];
}
}
return target;
}
var phone={
brand:'MI',
title:'K30S',
price:2999,
date:undefined,
memory:['8','256'],
power:{
GPS:'查看定位信息',
call(){
alert('打电话')
},
play(){
alert('玩游戏')
}
}
}
var phone2={};
deepCopy(phone2,phone);
phone2.memory.push(1024);
console.log(phone);
</script>
性能优化
一、函数防抖(debounce)
思路:
-
在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms
-
如果在200ms内没有再次滚动事件触发,那么就执行函数
-
如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时
效果:
-
如果短时间内大量触发同一件事,只会执行一次函数
实现:
-
借助定时器完成
<body>
<div>
…………
</div>
<script>
//滚动时,调用的其实是debound里面返回的匿名函数
window.onscroll = debounce();
function debounce() {
//每次滚动的时候,不断的清楚上一次还未执行的定时器,然后再启动新的定时器
var timer = null;
//高频次的滚动,不断的清楚定时器,减少函数的执行次数
return function() {
clearTimeout(timer);
timer = setTimeout(function() {
var top = document.body.scrollTop || document.documentElement.scrollTop;
console.log(top);
}, 100)
}
}
</script>
</body>
二、函数节流(throttle)
问题:
-
希望即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈
思路:
-
连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率,类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活
<script>
//节流
//滚动时,调用的其实是throttle里面返回的匿名函数
window.onscroll = throttle(500)
function throttle(delay) {
var start = 0; //初始化第一次执行的时间点
return function() {
//每次滚动时调用内部函数
var now = Date.now();
//用当前时间-上一次执行的时间
var sub = now - start;
//计算是否超过了500毫秒
if (sub > delay) {
//如果超过了500毫秒,去执行一次代码
var top = document.body.scrollTop || document.documentElement.scrollTop;
console.log(top);
start = now
}
}
}
</script>
键盘输入事件,时隔多少秒获取一次
<input type="text">
<script>
var input = document.querySelector('input');
// 键盘输入节流
input.onkeyup = delay(500);
function delay(time) {
var start = 0;
return function() {
var now = Date.now()
var sub = now - start;
if (sub > time) {
console.log(input.value);
start = now
}
}
}
//键盘输入防抖
input.onkeyup = play()
function play() {
var timer = null;
return function() {
clearTimeout(timer)
var timer = setTimeout(function() {
console.log(input.value);
}, 200)
}
}
</script>
防抖和节流的区别
防抖
-
事件结束后多长时间后才会执行
-
如果一直在滚动,就一直不会执行,只有滚动结束后,停止滚动间隔一段时间后才会执行
节流
-
隔一段时间就执行一次
-
滚动每间隔多长时间就执行一次