全局变量
变量分类:
- 全局变量:定义在function外部的变量。特点:不销毁
- 局部变量:定义在function内部的变量。特点:用完即销毁
var username = '女' // 全局
var play () {
var username = '女' // 局部
}
特殊:
var play () {
username = '女' // 全局
}
使用场景
全局:少用,一直常驻内存中不易被销毁,容易出现命名冲突,适合公用的变量
局部:函数执行完毕即被销毁,所以无法持久化
ready == DOMContententLoaded
作用域
函数作用域:函数内部使用范围
全局作用域:整个网页范围
面试题
function fn(a) {
console.log(a) // {函数体} 原因:函数的优先级大于var
var a = 20
function a() {}
console.log(a) // 20
}
面试题
let块级作用域
特点:
- 块级作用域
- 暂时性死区
- 当全局变量使用的时候不属于window对象
面试题
<script>
for(var i=0;i<3;i++){
setTimeout(function(){
alert(i) // 4,4,4
},0)
}
</script>
<!-- let 每次执行的时候会生成一个副本 -->
<script>
for(let i=0;i<3;i++){
setTimeout(function(){
alert(i) // 0,1,2
},0)
}
</script>
不能在初始化之前去访问let修饰的变量:暂时性死区
function run(){
console.log(x); // 报错
let x = 10;
}
用let声明的变量,不属于window(用var声明的变量属于window)
const
命名:要求全部大写,多个单词之间下划线隔开
特点:
- 定义的时候就需要初始化他的值,且后续无法修改
- 如果用const定义一个对象,那么是可以修改内部属性的
- 当全局变量的时候,效果和了他一样
if(1 == 1){
const ONE = 10;
}
console.log(ONE)
const play = { name: "张三", age: 18 };
// play = {}; // 报错:Assignment to constant variable.
console.log(play.name); // 张三
// 如果const定义的是一个对象,那么是可以修改它内部属性的
play.name = "李四";
console.log(play.name); // 李四
IIFE(立即执行函数)
语法:(function([形参]){
// 函数体
[return]
})([实参])
作用:
- 早期模块化解决方案,避免命名冲突
- 防止外部代码来访问内部的变量,提高安全性
*闭包
好处:能够读取其他函数内部变量的函数
坏处:用多了后,增加了内存的消耗,容易造成内存的泄露
特点:外函数包裹内函数,而且内函数的可以访问外部函数的局部变量,闭包环境是内部函数与外部函数的桥梁
<script>
var fn = (function(){
var num = 10;
return function(){
num++;
console.log(num)
}
})()
// 在外部执行fn,不论执行多少次,num变量一直存在,从未还原过
// 好处:num既有了全局变量的常驻内存不销毁的特点,并且num又不会和外部变量重名
// num一直被内部函数(fn)使用,所以他不会被垃圾回收器收走
fn(); // 11
fn(); // 12
// 如和让GC回收掉num
// 没有地方用num,自然会被垃圾回收机制收走
fn = null;
</script>
没有任何地方使用的变量,会被垃圾回收器收走(释放内存空间)
this指向问题
this指向window
function 函数名(){}
var 函数名 = function(){}
this指向当前对象
var obj = {
函数名:function(){}
}
this指向按钮
btn.onclick = function(){}
面试题
独立的创建一个函数,不论写在那,都属于window
<script>
let Obj = {
getNameFunc:function(){
console.log(this) // Obj
return function(){
console.log(this) // window
}
}
}
</script>
arguments(参数)
含义:当前函数的实参列表
arguments.callee
含义:当前函数
call()与apply()
相同点:都是调用函数,并且动态修改函数里的this值
不同点:
call(新this值,参数1,参数2····)
apply(新this值,[参数1,参数2····])
slice截取的时候,找的是 this
let args = [7,8,9].slice();//还是7,8,9 从原数组截取的
let args =[7,8,9].slice.call([1,2,3]);//1,2,3,从后面这个数组截取的
函数柯里化
一个函数只接收一个参数
function sum(){
//调用slice方法,但是把slice里面的this改成arguments,然后产生一个新数组
let args =[].slice.call(arguments);//1,3
function exec(){
args.push(...arguments);// args = [1,2,3,4]
return exec;
}
exec.calc=function(){
return args.reduce(function(total,current){
return total+current;
})
}
return exec;
}
// console.log(sum(1,3)(2,4)(100)(1));
var exec1 = sum(1,3);
var exec2 = exec1(2,4)
var exec3 = exec2(100)
var exec4 = exec3(1);
console.log(exec4.calc());
我的练习
<!-- 柯里化:一个函数只接收一个参数 -->
<script>
let arr = [];
function play() {
arr.push(...arguments);
let qq = 0;
for (let i = 0; i < arr.length; i++) {
qq += arr[i];
}
console.log(qq);
return play;
}
console.log(play(3)(4));
</script>
<script>
function play() {
let arr = [].slice.call(arguments);
function gogo() {
arr.push(...arguments);
let qq = 0;
for (let i = 0; i < arr.length; i++) {
qq += arr[i];
}
console.log(qq);
return gogo;
}
return gogo;
}
console.log(play(3)(4));
</script>
<script>
function play() {
let num = arguments[0];
function gogo() {
console.log(arguments[0]);
console.log(num.test(arguments[0]));
return gogo;
}
return gogo;
}
play(/^\d/)('c23');
</script>
匿名函数
优点:匿名函数,仅在调用时,才临时创建函数对象和作用域链对象;调用完,立即释放,所以匿名函数比非匿名函数更节省内存空间
eval
执行上下文栈
- 全局执行上下文
- 函数执行上下文
- eval执行上下文【很少使用】 assign:分配
执行上下文调用栈【代码的执行流程】【先进后出】
- 全局上下文入栈
- 函数上下文入栈
- 函数上下文出栈
- 全局上下文出栈
递归
概念:函数内部调用自身
递归如果不设置终点,则会死掉
<!-- 面试题一 【阶乘】 -->
<script>
function play(){
let num = arguments[0]; // 获取传入的数字
if(num==1){
return 1;
}
return play(num-1)*num;
}
play(10) // 乘到十
</script>
方法二
<!-- 递归:自己调自己 -->
<script>
function play(num) {
if (num == 1) {
return 1;
}
// 等于是play()在输入时执行,后面的乘法在所在函数输出的时候执行
return play(num - 1) * num;
}
console.log(play(10));
</script>
<!-- 面试题二 【快速排序】 -->
<!-- 1.在数据集之中,选择一个元素作为"基准"
2.所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
3.对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。 -->
<script>
let arr = [1, 5, 3, 9, 4, 8, 0, 2, 12, 7, 2, 9];
function my_arr(arr) {
// 如果只剩下一个或一个都不剩直接返回(防止空转导致内容溢出)
if (arr.length <= 1) {
return arr;
}
let first_arr = arr[0]; // 获取第一个作比较
let left_arr = []; // 左边
let right_arr = []; // 右边
let center_arr = [first_arr]; // 中间
for (let i = 1; i < arr.length; i++) {
// 如果比你大,去左边
if (first_arr > arr[i]) {
left_arr.push(arr[i]);
}
// 如果比你小,去右边
if (first_arr < arr[i]) {
right_arr.push(arr[i]);
}
// 如果相等,放中间
if (first_arr == arr[i]) {
center_arr.push(arr[i]);
}
}
// 左边的执行完毕,才能执行右边的
return [].concat(my_arr(left_arr), center_arr, my_arr(right_arr));
}
console.log(my_arr(arr));
</script>
浅拷贝(主要针对对象)
复制出来的对象做修改时,对原对象照成影响的,都叫浅拷贝。
var a = {
name:'rose',
age:18,
work:['啦','拉拉','啦啦啦']
}
var b = a; // 复制的是引用的地址【浅拷贝】
b.name='jack'
console.log(a.name); // jack
// ... 展开符只能展开一层,但是a里面还有一个对象,就是work数组
var c = {...a};
c.name = '张三';
// c和a是不同的两个对象,但是c和a他们两共用一个work
c.work[0] = '嘻嘻';
console.log(a.work[0]); // 嘻嘻
// 情况三:通过Object.assign()修改复制的对象的对象(类似于情况二)
let x = {};
let y = { name: "李四", age: 21, arr: [1, 2, 3] };
Object.assign(x, y);
x.arr.push(...[4, 5, 6]);
console.log(x);
console.log(y);
深拷贝
<!-- 深拷贝 -->
<script>
// 方法一:通过JSON的方式(函数与未定义的元素会被搞丢)
let obj = {
name: "张三",
love: ["丽丽", "美美", "笑笑"],
work: {
k1: "卖画",
k2: "卖唱",
},
fun: function () {
console.log("起飞");
},
und: undefined,
};
let z = JSON.stringify(obj);
let obj2 = JSON.parse(z);
console.log(obj2);
// 方法二:通过递归的方式一层一层的剥
let obj3 = {};
function play(obj, obj3) {
// for...in...(当中遍历对象i取的就是属性名,遍历数组取的就是下标)
for (let i in obj) {
if (typeof obj[i] == "object") {
let p;
if (obj[i].length) {
p = [];
} else {
p = {};
}
obj3[i] = play(obj[i], p);
} else {
obj3[i] = obj[i];
}
}
return obj3;
}
play(obj, obj3);
</script>
instanceof:运算符
查找
-
循环数组,逐个比较
-
数组.indexOf(XXX)
-
二分查找法(折半查找法):前提是有序数组
<script> var arr = [...]; // 由小到大 //获取他的中间的值 let arr_start = 0; // 获得开始的坐标 let arr_post = arr.length-1; // 获取结束的坐标 let arr_midden = parseInt(arr_post/2); // 取得中间的下标 function play(arr,num){ // 输入形参 while(arr[arr_midden] != num){ if(arr[arr_midden] > num){ // 当中位数大于参数2时 arr_post=arr_midden-1 } if(arr[arr_midden] < num){ // 当中位数小于参数2时 arr_start=arr_midden+1 } arr_midden=parseInt((arr_post + arr_start)/2) } return arr_midden } play(arr,10) </script>
性能优化
- onscroll
函数防抖
<script>
let ms = null;
window.onscroll = play; // 当全局的滚动条移动时,触发play属性。(思考:如果调用的不是play对象而是play对象的返回值,那么值就会在开始确认下来,并且再也不会变化。)
function play(){
clearTimeout(ms); // 滚动条滚动的时候不停的触发关闭定时器事件
ms = setTimeout(function(){
console.log(document.documentElement.scrollTop || document.body.scrollTop)
},1); // 当滚动条在该位置路过200ms后触发,但是由于上方有关闭定时器,所以只有滚动条停止后才触发
};
</script>
函数防抖-优化
<script>
window.onscroll = play();
function play(){
clearTimeout(ms); // 滚动条滚动的时候不停的触发关闭定时器事件
ms = setTimeout(function(){
console.log(document.documentElement.scrollTop || document.body.scrollTop)
},1); // 当滚动条在该位置路过200ms后触发,但是由于上方有关闭定时器,所以只有滚动条停止后才触发
};
</script>
函数节流
<script>
let old_date = Date.now(); // 获取程序运行时的时间
window.onscroll = play;
function play() {
let new_date = Date.now(); // 执行这句程序时的时间
if (new_date - old_date >= 400) {
console.log(
document.documentElement.scrollTop || document.body.scrollTop
);
old_date = Date.now(); // 更新程序运行时的时间
}
}
</script>
函数节流-优化
<script>
window.onscroll = play();
function play(){
let lod_date = Date.now() // 获取程序运行时的时间
return function(){
let new_date = Date.now(); // 执行这句程序时的时间
if((new_date - old_date) == 400){
console.log(document.documentElement.scrollTop || document.body.scrollTop);
old_date = Date.now(); // 更新程序运行时的时间
}
}
}
</script>
seo优化
websoced
性能优化
图表
vue新特性
响应式原理