es基础
1.变量 1.1var,let,const var 全局作用域和函数作用域,及只要申明的代码执行了,下面的代码也能使用该变量如例一 挂载带wondow上,可以先打印后声明(打印undefined,不会报错,这就是变量提升),可重复申明,后申明的会覆盖前面申明的 let 块级作用域,声明的变量只能在当前块{}的申明后面与子块{{}}中使用,for()里申明的变量只能作用于for{}中 不会挂载带wondow上,不能先打印后声明(会报错,打印与申明之间会形成暂时性死区,let变量也会提升,但是暂时性死区的缘故导致报错) 子块可调用父块变量,如果子块也申明跟父块一样的变量名,则子块的变量同意遵循在当前块{}的申明后面与子块{{}}中使用 //例一 if (1) { var a = 11; let b= 22; } console.log(a); //正常,打印11,如果if(false)就不正常 console.log(b); //不正常 //例二 var a = []; for (let i = 0; i < 5; i++) { a[i] = function () { console.log(i); }; } //for (let i = 0; i < 5; i++) 打印3,每循环一次,i的作用域仅限该次循环{}中 //for (var i = 0; i < 5; i++) 打印5,循环体已结束,此时调用打印最终结果5 a[3](); //例三 var a = 11; console.log(window.a); //打印11 let a = 11; console.log(window.a); //打印undefined //例四 console.log(a); //打印undefined var a = 11; console.log(b); //报错 var b = 11; //例五 let a = 11; { console.log(a); //正常,使用父级a } let b = 11; { console.log(b); //不正常,下面有b的申明,所以b是当前块的b而不是父级b let b = 11; } let c = 11; { let c = 22; console.log(c); //正常 打印22 } const 常量,不能重新赋值(变量重新等于其他的值就报错),对象和数组可以修改里面的值,其他规则与let一样 const a = XXX; a = YYY; //重新赋值报错 a.bb = ZZZ; //正常 a.push = XXx; //正常 1.2解构赋值 基本用法:数组的解构用[],对象的解构用{},等号左边是解构出来的变量,右边是数据源,可以全部解构,也可解构部分,可赋默认值,也可用剩余扩展符...rest 例一 let [a,b] = [1,2,3]; console.log(a); //1 console.log(b); //2 例二 let {age,name,score} = {name:11,age:22} //对象必须用大括号,[age,name]报错,里面的键跟后面的键保持一致,不一致的取得是空 console.log(age); //22 console.log(name); //11 console.log(score); //undefined 数组的...运算符,数组解构可在数组最后面使用...运算符,表示数据源剩余数据,打印的是个数组 例一 let [a,b,...c] = [1,2,3,4,5]; console.log(a); //1 console.log(b); //2 console.log(c); //[3,4,5] 对象的k:v解构模式,对对象的键取别名解构,此时原键名被新的别名取代,使用必须用别名 例一 let {yingyu:english} = {yingyu:88}; console.log(english); //打印88 console.log(yingyu); //报错 解构赋值也可用作函数参数 例一 function fun([a,b]){ console.log(a+b); } fun([1,2]); //打印3 例二 function func({math,english}){ console.log(math+english); } func({math:60,english:70}); //打印130 1.3其他 2,数组 2.1form 数组对象转数组,静态方法,对象的属性名必须与数组的下标对应,必须有length属性,length是几,生成的数组就有几项,生成数组的下标对应对象的属性名,对不上就是undefined 例如 let obj={ 0:11, 1:22, length:2 } let arr = Array.from(obj); //[11,22] console.log(arr); 2.2of,静态方法,将一组值转成数组 let arr = Array.of(1,2,3,4); 2.3fill 对象方法,使用给定值,填充一个数组 let arr7=new Array(5).fill(3); console.log(arr7);//[ 3, 3, 3, 3, 3 ] 2.4find,对象函数,遍历数组,返回第一个返回true的项,找不到返回undefined,参数是回调函数,回调函数有三个参数,代表数组的值,下标,原数组 let arr = [1,2,3,4,3]; let theItem = arr.find(function(val,index,a){ if (val%3 === 0) { return true; } }); console.log(theItem); //3 2.5findIndex,对象函数,遍历数组,返回第一个返回true的下标,找不到返回-1,用法同find let arr = [1,2,3,4,3]; let theItem = arr.findIndex遍历数组(function(val,index,a){ if (val%3 === 0) { return true; } }); console.log(theItem); //2,3的下标正式2 2.6includes(str)是否包含某些,返回布尔型 2.7for of遍历 let arr = [11,22,33]; for(let v of arr){} for(let(k,v) of arr.entries){} 3,对象 3.1属性简洁表示,如果kv相同可省略v, let name = 'Li'; let obj = {name};//相当与{name:name} 3.2属性名表达式,属性名用[]包裹,及表达式变量名,即[变量名] 变量值为属性名 let key = 'name'; let obj = {[key]:'Li'}; console.log(obj.name); 3.3新增方法 is assign keys values entries freeze 4,类 4.1es5写法 function Person(name,age){ this.name = name; this.age = age; this.sayAge = function(){ return this.age; } } Person.prototype.sayName = function(){ return this.name; } function Ming(){} Ming.prototype = Person.prototype; //继承Person类的原型方法 let p = new Person("Li",12); console.log(p.sayName()); 4.2 es6写法 class Anim{ constructor(name,age){ this.name = name; this.age = age; } function sayName(){ console.log(this.name); } function sayAge(){ console.log(this.age); } } //继承 class Dog extend Anim{ constructor(name,age,color){ super(name,age); this.color = color; } function sayColor(){ console.log(this.color); } //重写父级方法 function sayName(){ console.log('dog name'+ this.name); } } let dog = new Dog('Li',12,'red'); dog.sayName(); 5,函数 5.1默认值 function(a=1,b=2){} //普通写法 function([a=1,b=2]=[]){} //数组解构写法 function({a=1,b=2}={}){} //对象解构写法 5.2rest参数(形式为...变量名),用于获取函数的多余参数,必须放在函数形参最后面 function demo4(a,b,...abc){console.log(abc);} demo4(1,2,3,4,5);//[ 3, 4, 5 ] 5.3严格模式,只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。一般把严格模式加在全局 5.4name属性,函数的name属性,返回该函数的函数名。 function demo(){} demo.name //demo 5.5箭头函数 ()=>{} (1)this作用域 (2)不可做构造函数 (3)没有arguments对象,使用rest参数替代 5.6call,apply,bind,都是为了改变this指向,第一个参数是this执行,不用改变执行,就传null call(this, 1, 2,...); //第一个参数是函数里this的指向,后面的参数与aa函数的参数对应(不对应有几个取几个),会直接执行函数 apply(this,[1,2,...]) //第一个参数是函数里this的指向,第二个参数是个数组,跟函数的参数对应,会直接执行函数 bind(this,1,2,...) //第一个参数是函数里this的指向,后面的参数与aa函数的参数对应(不对应有几个取几个),不执行函数,要执行,后面加() 例如取最大值 Math.max(1,2,3) let arr = [1,2,3]; console.log(Math.max(...arr )); console.log(Math.max.apply(null,arr)) console.log(Math.max.call(null,...arr)) console.log(Math.max.bind(null,...arr)()) //不会执行,要执行,后面加() 6,symbol类型 6.1原始数据类型(其他六种数据类型string,number,boolean,null,undefined,array,object),表示独一无二的值 6.2使用方式如下,直接调用Symbol方法,可带一个字符串参数 let s = Symbol(); console.log(typeof s); //symbol console.log(s); //Symbol() let ss = Symbol('haha'); console.log(typeof ss); //symbol console.log(ss); //Symbol(haha) 6.3在对象中作为对象的属性使用,操作属性必须用[变量名] let s = Symbol(); let obj = { [s] : 100 } console.log(obj[s]); //100 6.4对象中Symbol属性,无法通过for in,Object.keys() 遍历出来 要遍历出来,可以用Object.getOwnPropertySymbol(obj) 或者 Reflect.ownKeys(obj)遍历出来 7,模块module 7.1 import导入 export导出基本使用 import script标签必须添加type='module'属性,import from '文件路径',模块文件不用通过src引入 import * as f from xxx 导入所有项目,要获取export default,可用f.default export可导出变量,常量,函数,对象,类,可单独导出,也可通过export {name,sayName,Person}进行多个导出,注意一点,函数的作为多个导出必须放大括号内 export普通导出的项,import 必须放到大括号内导入 export default默认导出项,模块文件内最多只有一个,import不用放大括号内导入 ./js/index.js代码如下 export const name = 'Li' export function sayName(){ } export const man={ } export default class Person{ function constructor(){ } } <script type='module'> import Person,{name,sayName,man} from './js/index.js' </script> 8,代理 8.1proxy //通过代理,操作源数据,源数据可以是对象,也可以是数组 //target 源数据,key 要操作的数据key,value 要对key赋的值,receiver代理对象 let obj={ name:'lilei', age:21, sex:'男' } obj=new Proxy(obj,{ get(target,key,receiver){ //return target[key] //为什么不用这种做法,这种做法也能达到目的,但是target[key] 本身也是获取数据,导致也走了某个代理,所以最好用下面这种写法 return Reflect.get(target,key,receiver); }, set(target,propKey,value,receiver){ //return target[key] = value return Reflect.set(target,propKey,value,receiver); } }); console.log(obj.name);//1. false---- 2.结果:lilei obj.name='小明'; console.log(obj.name);//结果:小明 8.2defineProperty let obj={ name:'Li', age:21, sex:'男' } let internalName = obj.name; //必须需要这个中间变量 Object.defineProperty(obj,'name',{ get:function(){ //千万不能写出obj.name 这么写会导致回调陷阱,即获取操作又走到get,又执行obj.name,获取操作又走到get,最终导致内存不足的错误Maximum call stack size exceeded return '老名字是:'+internalName }, set:function(newValue){ internalName = '新名字是:'+newValue } }); console.log(obj.name); obj.name = 11 console.log(obj.name); 9,扩展运算符 10,迭代器与生产器 10.1迭代器Iterator,一种新的遍历机制,通过next()获取遍历得到的值,值的形式为{value:xxx,done:false};当值的done为true时,遍历不出东西了 举例:将数组使用迭代器遍历 let arr = [11,22]; let i = arr[Symbol.iterator](); console.log(i.next());//{value: 11, done: false} console.log(i.next());//{value: 22, done: false} console.log(i.next());//{value: undefined, done: true} 10.2生成器Generator,一个函数,通过yield表达式,中断执行,应用为使异步代码同步执行 10.2.1生成器基本声明方式 function* func(){ yield 1; yield 2; return 3; } let g = func(); //返回一个生成器对象,但是函数里面的代码不会执行 console.log(g.next()); //{value: 1, done: false} 调用next方法才会执行函数里面的代码,遇到yield就终止,next方法的返回值为yield的值 console.log(g.next()); //{value: 2, done: false} console.log(g.next()); //{value: 3, done: true} value为return的值,如果生成器函数没有return,value的值就为undefined,done为true表示遍历完成 10.2.2next带参数的形式 yield会中断代码的执行,所以let x = yield 1;不会执行,需要在下次next执行,而x就等于下次next的参数 function* add(){ let x = yield 1; let y = yield 2; return x+y; } let g = add(); console.log(g.next()); //yield 1; 打印{value:1,done:false} console.log(g.next(11)); //let x = 11; yield 2; 打印{value:2,done:false} console.log(g.next(22)); //let y = 22; return x+y; 打印{value:33,done:true} 10.2.3应用实例 例一解决回调地狱 let request = (url)=>{ $.get({ url:url, success(res){ g.next(res); //取得数据,把数据付给main函数里的res变量,g是下面生成器变量,已经变量提升了,let也会提升,这种异步先使用在声明不会产生错误 } }); } function* main(){ let res = yield request('https://v1.yiketianqi.com/api?unescape=1&version=v63&appid=&appsecret='); console.log(res); console.log('请求完成,继续操作'); } let g = main(); g.next(); //先让main跑起来,跑到yield里面发送请求 最终打印结果 /* {errcode: 100, errmsg: '缺少注册参数appid或appsecret 请仔细看文档,账号注册地址 http://tianqiapi.com'} 请求完成,继续操作 */ 例二 按照loading 请求数据 loadClose的顺序完成 function loading(){ console.log('loading'); } function requestData(){ setTimeout(()=>{ let data = '这是模拟的数据' console.log('获取了初始数据'); g.next(data); },1000); } function loadClose(){ console.log('loading close'); } function* load(){ loading(); let res = yield requestData(); console.dir('获取异步数据:'+res); loadClose(); console.log('loading完成,继续操作'); } let g = load(); g.next(); 打印结果 /* loading 获取了初始数据 获取异步数据:这是模拟的数据 loading close loading完成,继续操作 */ 11,promise async await 12,set与hashmap 13,字符串 13.1模板字符串,``符号包裹的则是模板字符串,里面${变量名}是变量 例如 let name = 'Li'; let str = `${name}爱洗澡` console.log(str); //Li爱洗澡 13.2标签模板 13.3新增函数 13.3.1 includes 是否包含字符串 13.3.2 startsWith 是否包含字符串 13.3.3 endsWith 是否包含字符串 13.3.4 repeat 将字符串重复几次,不会改变源字符串数据 例子 let str = '我爱北京' console.log(str.includes('爱'));//true console.log(str.startsWith('我'));//true console.log(str.endsWith('我'));//false console.log(str.endsWith('北京'));//true console.log(str.repeat(2)); //我爱北京我爱北京 console.log(str); //我爱北京 14,原型和原型链 14.1原型是保存公共内容(公共变量,公共函数==)的区域。申明一个类,就会自动创建一个原型prototype 14.2类实例化后的对象,会为对象添加一个__proto__属性,该__proto__属性指向类的prototype,所以对象.__proto__ === 类的prototype function Anim(){ } let a = new Anim(); console.log(Anim.prototype === a.__proto__); //true //a.__proto__指向类的原型,打印的是cunstructor Anim() f [[prototype]] //a.__proto__.__proto__指向Object的原型,打印的是cunstructor Object() f [[prototype]] 还有大量方法hasOwnProperty,isPrototypeOf,propertyIsEnumerable== //object的__proto__是显示的,在控制台能看到,上面的控制台看不到,但能打印出来 //a.__proto__.__proto__.__proto__ //打印null,最终的原型是null console.log(a.__proto__.__proto__.__proto__);//打印null 14.3类的方法申明在原型上的作用 (1)节约内存 function Anim(){ } Anim.prototype.sayName = function(){} let a1 = new Anim(); //不会为对象开辟sayName方法的内存,从原型上取 let a2 = new Anim(); //不会为对象开辟sayName方法的内存,从原型上取,这样sayName只要在原型上申明一次即可,无论创建多少对象,都可以从原型获取这个方法 (2)通过原型继承 function Anim(){} Anim.prototype.name = 'aa' function Dog(){} Dog.prototype = Anim.prototype let d = new Dog(); console.log(d.name); 14.4数组的对象原型是Array 14.5对象的原型是Object let arr = []; //打印的是cunstructor Array() f [[prototype]] 还有大量跟数组有关的方法find,findindex,includes,indexof== console.log(arr.__proto__); let obj = {}; console.log(obj.__proto__); 14.6类的原型prototype,对象和数组的对象原型是__proto__.类实例化对象的__proto__指向类的prototype,__proto__本身也是个对象,它的__proto__指向下级(可能是object),最终指向null 15,深拷贝与浅拷贝 16,防抖和节流 16.1防抖debounce:是指单位时间内,频繁触发事件,只执行最后一次(每次触发事件,取消上次的执行,重新计算触发时间,导致最后一次执行的时间延后) 16.1.1 场景 (1)搜索框搜索输入,只需用户最后一个字符输入完,在发起请求 (2)手机号,邮箱验证检测 (3)div滑动事件,鼠标不动了,会在1s后获取坐标 例如 let box = document.querySelector('.box'); function mousemove(e){ box.innerHTML = e.clientX+" "+e.clientY; } function debounce(fn,t){ let timer; return function(e){ if (timer) clearTimeout(timer); timer = setTimeout(fn.bind(null,e),t); } } box.addEventListener('mousemove',debounce(mousemove,500)); 16.2节流throttle:是指一定时间内执行的操作只执行一次,即每段时间内只执行一次,resize,scroll 例如 let box = document.querySelector('.box'); function mousemove(e){ box.innerHTML = e.clientX+" "+e.clientY; } function throttle(fn,t){ let timer = null; return function(e){ if (!timer){ timer = setTimeout(function(){ fn(e); timer = null; //为什么不用clearTimeout,clearTimeout放在setTimeout里面无法清除定时器 },t); } } } box.addEventListener('mousemove',throttle(mousemove,500)); 16.3 效果总结, 防抖,鼠标一致动,坐标一致不变,直到鼠标停止500毫秒后,坐标才变成鼠标最后的位置坐标 节流,鼠标一致动,坐标每隔500毫秒变一次 17,addEventListener的几点研究 ///防抖和节流的知识储备 let box = document.querySelector('.box'); function mousemove(e,a=1,b=2){ //e必须放最前面,因为是直接调用,不带其他参数 box.innerHTML = e.clientX+" "+a; } function mousemoveRest(a,b,e){ //为函数绑定其他数据,e必须放最后面 box.innerHTML = e.clientX+" "+a; } function mousemoveGo(){ return function(e){ box.innerHTML = e.clientX; } } //addEventListener第二个参数是个函数 //绑定事件,回调函数不会执行 box.addEventListener('click',mousemove); box.addEventListener('click',mousemoveRest.bind(null,1,2)); //绑定其他参数 //绑定事件,回调函数会执行,下面这种情况导致触发click,也不会执行mousemove,mousemove里也没有e //因为addEventListener第二个参数是个函数,而执行过后的mousemove不是一个函数,导致出问题 box.addEventListener('click',mousemove()); //函数名称后只要带上(),就会立即执行 //绑定事件,回调函数会执行 //相当于绑定里mouseMoveGo里面的那个匿名函数 //所以触发click,只会执行匿名函数里面的代码,匿名函数外的代码不会再次执行,e也再匿名函数内 box.addEventListener('click',mouseMoveGo()); //相当于下面这种写法 let go = mousemoveGo(); //go是mousemoveGo返回的那个匿名函数 box.addEventListener('click',go);