var 、let、const的区别
var | let | const |
---|---|---|
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
1. var
-
只有var关键字才有变量提升
-
变量提升到当前作用域最前面
-
只提升变量声明,不提升变量赋值
console.log(num);//undefined var num = 10;
-
经典面试题
var arr = []; for(var i=0;i<2;i++){ var全局变量 console.log(i)//0 1 arr[i] = function() { //循环时并没有执行这个方法 console.log(i)//2 } } console.log(i)//2 当i=2时循环结束,接着往下执行代码,此时function还没调用 //执行到这,循环已经结束了,方法本身没有i,根据作用域链查找原则,向上一层找到i,此时的i为全局变量i,值为2 arr[0]();//2 arr[0]是一个function,执行输出的i是全局变量i,此时i为2 arr[1]();//2 var arr = []; for(let i=0;i<2;i++){ let块级作用域,i=0为一个作用域,i=1为一个作用域 arr[i] = function() { console.log(i) //所以 i 向上一级作用域查找 } } console.log(i)//let块级作用域 i is not defined arr[0]();//0 向i=0的作用域查找 arr[1]();//1 向i=1的作用域查找
2. let
-
let关键字没有变量提升
console.log(num);//num is not defined let num = 10; var num = 10; if(true) { console.log(num);//报错 let num = 20;//声明变量,则形成块级作用域,与{}外定义的全局num没有关系 }
-
在大括号 {} 中使用 let 关键字声明变量才具有块级作用域,var不具备
if(true){ let num = 100; var abc = 200; } console.log(abc);//200 console.log(num);//num is not defined
-
防止循环变量变成全局变量
for(let i= 0; i<2; i++){ } console.log(i);//i is not defined 用var可以访问到为2
3.const 声明常量
-
具有块级作用域
if(true){ const a = 10; if(true) { const a = 20; console.log(a);//20 } console.log(a);//10 } console.log(a);//a is not defined
-
声明的常量必须赋初始值
const a; //Missing initializer in const declaration
-
常量声明后值不可更改
1. 基本数据类型,值不可更改 const a = 1; a = 2;//Assignment to constant variable. 2. 复杂数据类型,数据结构内部的值可以更改,数据值本身不可更改(也就是说常量值对应的内存地址不可改) const arr = [100,200]; arr[0] = 1; //arr = [1,200]; arr = [1,2];//Assignment to constant variable.
函数的定义
-
自定义函数(命名函数)
-
function fn() {};
-
-
函数表达式(匿名函数)
-
var fun = function() {};
-
-
利用 new Function('参数1','参数2','函数体');
-
var f = new Function('a', 'b', 'console.log(a+b)'); f(1,2);//3
-
-
所有函数都是Function的实例对象
-
函数也属于对象
函数的调用
this一般指向我们的调用者
-
普通函数 (this指向window)
function fn() {}
fn(); fn.call()
-
对象的方法 (this指向该方法所属对象)
var o = { sayHi:function() { console.log('hahaha') } } o.sayHi();
-
构造函数 (this指向实例对象,原型对象里面的方法也指向实例对象)
function Star() {} new Star();
-
绑定事件函数 (this指向绑定事件对象)
btn.onclick = function() {};//点击按钮调用
-
定时器函数(window)
setInterval(function(){},1000)
//定时器自动一秒调用一次 -
立即执行函数 (window)
(function() {})()
函数提升
-
foo()//调用函数 function foo(){ console.log('声明之前被调用'); } //不存在提升 bar()//错误 var bar = function(){ console.log('函数表达式不存在提升现象'); }
-
函数表达式不存在提升现象
-
函数提升到当前作用域最前面
箭头函数
-
const fn = (i,v) => {return i+v};
-
形参只有一个可以省略小括号, 函数体只有一句代码且执行结果就是函数的返回值也可以省略大括号
const fn = v => alert(v);
-
箭头函数没有自己的this,指向定义箭头函数位置中的this
function fn(){ console.log(this); return () => { console.log(this);//箭头函数this指向定义其位置中的this,即指向函数作用域中的this } } const obj = {name:'Lucy'}; const result = fn.call(obj);//改变this指向obj result();
-
面试题
var age = 100; var obj = { age: 20, say: () => { //由于obj没有作用域,所以箭头函数定义在window身上 alert(this.age);//若全局没有age,则弹出undefined } } obj.say();//100
改变函数内部this指向
1. call方法
-
可以调用函数
-
可以改变函数内的this指向
var o = { name:'Lucy' } function fn(){ console.log(this) } fn();//this指向window fn.call(o,1,2);//this指向o对象
-
主要作用可以实现继承
function Father(uname,age){ this.uname = uname, this.age = age } function Son(uname,age){ Father.call(this,uname,age) } var son = new Son('Lucy',18); console.log(son);//Son {uname: 'Lucy', age: 18}
2. apply方法
-
可以调用函数
-
改变函数内部的this指向
-
它的参数必须是数组(伪数组)
function fn(arr){ console.log(this);//this指向o console.log(arr);//'pink' } fn.apply(o,['pink']);//参数不是数组会报错
-
apply主要应用 求最大最小值
var arr = [1,66,2,99,3] Math.max.apply(Math,arr);//99 Math.min.apply(Math,arr);//1
3.bind方法
-
不会调用原来的函数
-
改变原来函数内部的this指向
-
返回的是原函数改变this之后产生的新函数
var o = { name:'Lucy' } function fn(a,b){ console.log(this);//指向o console.log(a+b);//3 } fn.bind(o,1,2);//不会调用 //返回新函数 var f = fn.bind(o,1,2) f();
-
bind应用
var btns = document.querySelectorAll('button'); for(var i=0;i<btns.length;i++){ btns[i].onclick = function(){ this.disabled = true;//指向btn setTimeout(function(){ this.disabled = false;//用bind改变this指向btn }.bind(this),3000); //这个this指向btn,本来指向window } }
高阶函数
function fn(a,b,callback){
console.log(a+b);
// 表示如果传入了callback,那么就调callback(),否则不调用
callback && callback();
}
fn(1,2,function(){
console.log('我是最后调用的');
})
递归函数
函数内部自己调用自己 递归函数必须加退出条件,否则栈溢出
var num = 1;
function fn(){
console.log('我要打印6句话');
if(num == 6) {
return;//递归函数必须加退出条件
}
num++;
fn();
}
fn();
-
利用递归求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n){ if(n == 1){ return 1; } return n * fn(n - 1); } console.log(fn(3));//6 //思路 //return 3 * fn(2) //return 3 * (2 * fn(1)) //return 3 * (2 * 1) //return 6
-
利用递归求斐波那契数列(兔子序列)1、1、2、3、5、8、13、21... 只需要知道 n 的前两项 (n-1 n-2)就可以计算出 n 对应的序列值
function fn(){ if(n == 1 || n ==2){ return 1 } return fn(n - 1) + fn(n - 2); } console.log(fn(6));//8
-
利用递归得到数组里层数组的数据 输入id可以返回数组对象
var data = [{ id:1, name:'水果', goods : [{ id:11, name:'香蕉', goods : [{ id:22, name:'熟的', }, { id:23, name:'生的' }] }, { id:12, name:'葡萄' }] },{ id:2, name:'蔬菜' }]; function getName(data,id) { var showObj = {}; data.forEach((item)=>{ if(item.id == id){ o = item; }else if(item.goods && item.goods.length > 0){ //递归,把里层item传进去再调用自己本身可实现再遍历,避免再写一层forEach o = getName(item.goods,id) } }) return o; } getName(data,2)//{id: 2, name: '蔬菜'} getName(data,12)//{id: 12, name: '葡萄'} getName(data,22)//{id: 22, name: '熟的'}
浅拷贝
-
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。 ES6新方法:
Object.assign(o,obj)
var obj = { id:1, name:'Lucy', msg:{ age:18 } } var o = {}; //1. for(var k in obj){ o[k] = obj[k]; } //2. Object.assign(o,obj) console.log(o); o.msg.age = 20; console.log(obj);//会改变原对象数据
-
浅拷贝是把地址拷贝给新对象 指向同一个数据,所以修改o,obj也会跟着改变
-
深拷贝
-
深拷贝拷贝多层,每一级别的数据都会拷贝
var obj = { id:1, name:'Lucy', msg:{ age:18 }, color:['pink','red'] } var o = {} function deepCopy(newobj,oldobj){ for(k in oldobj){ var item = oldobj[k]; console.log(item); 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); console.log(o); o.msg.age = 20; console.log(obj);//不会改变原数组