一、预编译
1、作用域的创建阶段:创建Ao对象—>找形参和变量的声明,作为Ao对象的属性名,值为undefined—>实参和形参相统一—>找函数声明,会覆盖变量的声明。
2、预编译阶段:逐行执行。
二、This
1、在函数中直接使用
2、函数作为对象夫人方法被调用,谁调用,指向谁。
3、get(‘111’) 相当于get.call(window,’111’)
4、Person.get(111) 相当于 Person.get.cal(Person,111)
5、箭头函数中的this在定义函数的时候绑定,非执行的时候绑定,也就是说this是继承自父执行上下文中的this(注意不是平级), 箭头函数没有this,(因此不能用做构造函数)箭头函数内部的this就是外层代码块的this也即是父级的this。
三、深拷贝和浅拷贝:
1、浅拷贝是创建一个对象,这个对象有着原始对象属性值的一份精确拷贝;如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所有如果其中一个对象改变了这个地址,就会影响到零一对象。
2、深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不影响原对象。
3、浅拷贝和赋值的区别:
当我们把对象赋值给一个新的变量时,赋的其实是该对象在栈中的地址,而不是堆中的数据,也就是两个对象指向同一个存储空间,无论哪个对象发生改变,其实都是改变存储空间的内容,因此两个对象是联动的;
浅拷贝是重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会互相影响.(Object.assign、…、concat、lodash、clone)
深拷贝:从堆内存中开辟一个新的区域存放对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。($.extend、json.parse、deepClone)
赋值:同一个地址;浅拷贝:
深拷贝:
function deepClone(obj){
var cloneobj =new obj.constrcutor
If(obj===null){return null}
If(obj instanceof Data ){return new Data(obj)}
If(obj instanceof RegExp ){return new RegExp(obj)}
If(type of obj !=’obj’){return obj}
for (var i in obj){
If (obj hasowProperrty(i)){
cloneObj[i] = deepClone(obj[i])
}
}
return obj
}
A instanceof B: B的prototype of 是否在A的原型链上。
四、防抖函数、节流函数
1、防抖函数:持续触发一个事件,在一定时间内没有触发,才执行操作;若在规定时间没有到来,再次触发是事件,就会重新计时。实现:用闭包。应用:图片懒加载。
2、节流函数: 当持续触发事件的时候,保证一段时间内,只调用一次事件处理函数,一段时间内,只做一件事。实际应用:表单的提交。案例:鼠标不断点击触发,规定在n秒内多次点击只有一次生效。
五、js作用域和闭包
1.全局作用域:
(1)全局作用域在页面打开是被创建,在页面关闭是被销毁。
(2)编写在script标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到
(3) 在全局作用域中有全局对象window,代表一个浏览器窗口,有浏览器创建,可以直接调用
(4)全局作用域中声明的变量和函数会作为window的属性和方法保存。
2. 函数作用域
(1) 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁。
(2) 每调一次函数,就会创建一个新的函数作用域,他们之间相互独立。
(3)在函数作用域中可以访问到全局作用中的变量,在函数外无法访问到函数作用域中的变量
(4)在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数上一级作用域中寻找,一直到全局作用域。
3. 作用域的深层次理解执行期的上下文
(1) 当函数代码执行的前期,会创建一个执行期上下文的内容对象AO(作用域)
(2)这个AO中的对象是预编译时候创建出来的,因为当被函数被调用时,会先进行预编译
(3)在全局代码执行的前期会会创建一个执行期上下文的对象GO
4. 函数作用域的预编译
(1)创建AO对象 AO{ }
(2) 找形参和变量声明,将变量名和形参名 当作AO对象的属性名 值为undefined
(3)实参 形参相统一
(4)在函数体里面找到函数声明,值赋予函数体。
5. 全局作用域的预编译:
(1) 创建GO对象 ------>找变量声明,将变量名作为对象的属性名,值为undefined----->找函数声明 值赋予函数体。
6. 作用域链:
7. 闭包的实际应用:防抖和节流
六、js运行机制(单线程、解释性语言)
1、arguments:j箭头函数没有arguments 可以通过…运算符 (es6新特性)展开,是类数组对象 可以通过Array.prototype.slice.call (arguments)转化为数组。
2、function fun (){ let a= b= 0 //b会变为全局对象(把0赋值给b 由于没有b 会在引擎里面创建一个b)}
3、写操作会造成内存泄漏:闭包、意外的全局变量、被遗忘的定时器、脱离dom的引用(比如获取了一个dom的引用,后面元素被删除 了但是引用还在)。
4、什么是高阶函数:将函数作为 参数或返回值 的函数。
5、手写map
function map(arr, maocallback){
//检查参数是否正确
if(!Arrary.isArray(arr)||!arr.lenfth||typeof mapcallback !=='function'){
return[]
}else{
let result = []
for(let i = 0,len = arr.lenth;i<length;i++){
result.push(mapcallback(arr[i],i,arrr))
}
return resut
}
}
七、event-loop(事件循环机制)
1、event-loop 由三部分组成:调用栈、微任务队列、消息队列;event-loop 开始的时候,会从全局一行一行的执行,遇到函数调用,会压到调用栈中,被压入的函数被称为帧,当函数返回后会从调用栈中弹出。
2、js中的异步操作如fetch、settimeout、setinterval 压入到调用栈的时候里面的消息会进入到消息队列中去,会等到调用栈清空之后再执行。
3、promise、async、await的异步操作的时候会加入到微任务中去,会在调用栈清空的时候立即执行 调用栈中的微任务也会立马执行
八、单例模式
1、定以:只有一个实例,可以全局的访问。
2、主要解决:一个全局使用的类频繁的创建和销毁。
3、何使使用:当你想控制实例的数目,节省系统化资源的时候
4、如何实现:判断系统是否已有这个单例,有则返回,没有则创建。
5、优点:内存中只要一个实例,减少内存开销,尤其是2中所说 比如首页页面的缓存。
6、使用的场景:全局的缓存、弹窗。
7、实现:es5是使用闭包 es6使用class和静态方法(只能被本类使用,不能被类的实例使用)
九、策略模式
1、定义:定义一系列算法,把他们封装起来,并且它们之间可以互相替换。核心:将算法的使用和算法的实现分离开来。算法也可以理解为业务逻辑。
例子:表单验证
//策略模式 封装
//表单验证
//算法业务封装
var registerForm = document.getElementById('registerForm')
var strategies={
isNonEmpty:function (value,errorMsg){
if(value == ''){
return errorMsg
}
},
minLength:function(value,length,errorMsg){
if(value.length<6){
return errorMsg
}
},
isMoblie:function(){
if(!/^1[3]|[5]|[8][0~9]{9}$/.test (value)){
return errorMsg
}
}
}
//业务实现
var validatafun = function(){
var validataor = new Validator() //假设有一个验证类Validator new Validator
validataor.add(registerForm.username,'isNonEmpty','密码不能为空')
validataor.add(registerForm.password,'minLength:6','密码长度不能小于6位')
validataor.add(registerForm.phonenumber,'isMobile','手机号码格式不正确')
//开启验证
var errorMsg = validator.start()
return errorMsg
}
registerForm.onsubmit = function (){
var errorMsg = validatafun()
if(errorMsg){
alert(errorMsg)
return false
}
}
//封装策略类 构造函数 class
var Validator = function (){
//保存验证规则数组
this.cache = []
}
Validator.prototype.add =function(dom,rule,errorMsg){
var ary = rule.split(':')
this.cache.push (function(){
var strategy = ary.shift() //用户选择的验证规则
ary.unshift(dom,value)
ary.push(errorMsg)
// return strategies(strategy).apply(dom,ary)
return strategies(strategy).apply(dom,ary) //用es6的解构扩展运算符 效果和上一句一样
} )
}
Validator.prototype.start =function(){
for (var i =0,varfunc;varfunc= this.cache[i++];){
var msg = varfunc()
if(msg){
return msg
}
}
}
十、发布订阅模式
1、定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当对象状态发生变化时,会自动通知已经订阅的对象。先订阅在发布
2、作用:(1)支持简单的广播通信,当对象状态发生变化时,会自动通知已经订阅的对象。
(2)可以应用在异步编写中,替代回调函数,可以订阅Ajax之后的事情,只需要订阅需要的部分(那么Ajax调用发布之后订
阅的就可以拿到消息了;不需要关心对象在一部运行时候的状态。)
(3)对象之间的松耦合,两个对象之间互相不了解,但是不影响通信,当所有的订阅者出现的时候,发布的代码无需改变,
同样发布的代码改变,只要之前约定的事件名称没有改变,也不影响订阅。
(4)vue react之间实现跨组件之间的传值。
3、实现:(1)确定发布者。
(2)给发布者添加一个缓存列表,用于存放回调函数来通知订阅者。
(3)最后是发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数。
十一、性能提升:
减少重定向,减少DNS查询,图片懒加载,使用本地缓存,压缩资源,减少http请求,使用CND(内容发布网络),使用外部JS和css,避免使用CSS表达式,将样式文件放在头部,js代码放在底部(浏览器的运行机制),
降低请求量:合并资源,减少HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。
加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。
缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。
渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline。
十二、函数闭包:
函数调用函数就会产生闭包,闭包就是函数的变量集合,只是这些变量在函数返回后继续存在;闭包就是函数的堆栈,在函数返回后并不会释放;闭包是指有权访问另一个函数作用域中的变量的函数
MDN对闭包的定义:闭包是指那些能够访问自由变量的函数,或子函数在外调用,子函数所在的父函数的作用域不会被释放,
自由变量:在函书中使用的,不是函数参数也不是函数局部变量的变量,
闭包=函数+函数能够访问的自由变量,从技术讲,所有JS函数都是闭包,从实践上:满足1)即使创建它的上下文已经销毁,它仍存在;2)在代码中引入自由变量,才称为闭包。
应用:模仿块级作用域;保存外部函数的变量;封装私有变量。
十三、JS特性:
运行在客户端浏览器上;与操作系统无关,跨平台语言;不用预编译,直接解释执行;脚本语言,解释性语言;弱类型语言,灵活;
Virtual Dom
用Js对象结构表示的DOM树,然后用这个树构建一个真的DOM树,插入到文档中,当状态变更时,重新构造一个新的对象树;然后用新的树和旧的树做对比,记录两颗树的差异,把所记录的差异应用到所构建的真正的DOM树上,视图就更新了,virtual DOM本质上就是js和DOM之间做的一个缓存。
十四、JS中实现继的几种方式
(原型链继承,构造继承,实例继承,拷贝继承组合继承,寄生组合继承,)
1.原型链继承:
核心:将父类的实例作为子类的原型;
特点:1)非常纯粹的的继承关系,实例是子类的实例也是父类的实例;2)父类新增的原型方法原型属性,子类都能访问到。
缺点:1)来自原型对象的所有属性被所有实例共享;2)无法实现多继承;3)无法向父类构造函数传参。
实现方式:
1)定义父类型构造函数,2)给父类型的原型添加方法3)定义子类型的构造函数4)创建父类型的对象赋值给子类型的原型5)将子类型原型的构造属性设置为子类型6)给子类型原型添加方法7)创建子类型的对象,可以调用父类型的方法
function Cat(){
}
Cat.prototype = new Animal(); //将父类的实例作为子类的原型
Cat.prototype.name = ‘cat’;
2.构造继承:
核心: 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类
特点:1)解决了1中,子类实例共享父类引用属性的问题,2)创建子类时,可以给父类传递参数,
3)可以实现多继承,(call多个父类对象)
缺点: 1)实例只是子类的实例,并不是父类的实例;2)只能继承父类的实例属性和方法,不能继承父类的原型属性和方法
3)无法实现函数复用,每个子类都有父类实例函数的副本,影响性。
function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
由于call方法可以改变函数的作用环境,因此在子类中,对cat调用这个方法就是将子类中的变量在父类执行一遍,由于父类中是给this绑定属性的,因此子类自然就继承了父类的共有属性。
但是,这种类型的继承没有涉及原型prototype,所以父类的原型方法自然不会被子类继承,而要想被子类继承就必须把方法放在构造函数里,这样创建出来的每个实例都会单独拥有一份而不能共用,这样就违背了代码复用的原则。
3.实例继承:
核心:为父类实例添加新特性,作为子类实例返回
特点:不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:1)实例是父类的实例,不是子类的实例, 2)不支持多继承
function Cat(name){
var instance = new Animal(); // 创建父类的实例
instance.name = name || ‘Tom’; //为父类的实例添加新特性
return instance; //作为子类的实例返回
}
4.拷贝继承:
特点:支持多继承
缺点:1)效率低,内存占用高(因为要拷贝父类的属性)2)无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
this.name = name || ‘Tom’;
}
5.组合继承:(原型+构造)
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类 实例作为子类原型,实现函数复用。
特点:1)弥补了构造继承的缺点可以继承实例属性和方法,也可以继承原型和方法。
3)既是子类的实例,也是父类的实例。
4)不存在引用属性继承问题;可传参;函数可复用。
缺点:调动两次父类的构造函数,生成两份实例(子类实例将子类原型上的那份屏蔽了)
function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
Cat.prototype = new Animal(); //将父类的实例作为子类的原型
Cat.prototype.constructor = Cat;
6.寄生组合继承:
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就 不会初始化两次实例方法/属性,避免的组合继承的缺点。
function Cat(name){
Animal.call(this);
this.name = name || ‘Tom’;
}
(function(){
var Super = function(){};// 创建一个没有实例方法的类
Super.prototype = Animal.prototype;
Cat.prototype = new Super(); //将实例作为子类的原型
})();
十五、JS原型链
JS中一个构造函数默认带有一个prototype属性,这个属性值是一个对象, 同时这个protoype对象自带一个constructor属性,这个属性指向这个构造函数;
每个函数都有一个prototype属性即显示原型,这个属性指向了一个对象,这个对象正是调用该函数而创建的实例原型;
每个对象都会有_proto_属性,指向这个对象的原型
同时每一个实例都会有一个_proto_属性指向这prototype对象,称_proto_为隐式原型。
我们在使用一个实例的方法是,会先检查这个实例中是否有这个方法,如果没有就会检查这个prototype对象是否有这个方法。
new操作符做了什么事:new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。
十六、事件流、事件委托、冒泡原理
HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念
事件流:事件流描述的是从页面中接收事件的顺序,也即事件发生会在元素节点之间按照特定的顺序传播,这个传播过程称为事件流。
DOM2级事件流包括下面几个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
addEventListener:addEventListener 是DOM2级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。IE只支持事件冒泡。js代码只能执行捕获或者冒泡阶段中的一个阶段。
事件冒泡:元素自身的事件被触发后,如果父元素有相同的事件,那么元素本身的触发状态 就会传递,也就是冒泡到父元素,父元素的先沟通事件也会一级一级的根据嵌套关系向外触发,直到doucument/window,冒泡结束。有些事件无冒泡:如onblur onfous onmouseenter onmouseleave
事件委托:是利用冒泡阶段的运行机制来实现的,不在事件的发生地(直接dom)上设置监听函数,而是把一个元素响应事件的函数委托到另一个元素,一般是把一组事件元素的事件委托到他的父元素上,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。(减少内存消耗节约资源,适合动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制)
例:最经典的就是ul和li标签的事件监听,如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。
在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。
addEventListener()方法,用于向指定元素添加事件句柄,它可以更简单的控制事件,语法为
element.addEventListener(event, function, useCapture);
第一个参数是事件的类型(如 “click” 或 “mousedown”).第二个参数是事件触发后调用的函数。第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。事件传递有两种方式,冒泡和捕获事件传递定义了元素事件触发的顺序,如果你将P元素插入到div元素中,用户点击P元素,在冒泡中,内部元素先被触发,然后再触发外部元素,捕获中,外部元素先被触发,在触发内部元素,
十七、箭头函数和非箭头函数的区别
1.箭头函数没有this,需要通过查找作用域链来确定this的值,即如果非箭头函数包含着 一个箭头函数,this绑定的是最近一层非箭头函数的this值。
2.箭头函数没有自己的arguments对象,但可以访问外围函数的arguments对象。
3.不能通过new关键字调用箭头函数,同样也没有new.target。
十八、ES6新特性
在变量声明和定义方面新增let,const,有局部变量的概念,赋值中引入了解构赋值;
ES6对字符串(如模板字符串),数组,正则,对象(属性的简洁表达),函数(如默认参数)等扩展了一些方法;
引入新的数据类型:symbol,新的数据结构:set,map;为解决异步回调问题引入:promise和generator;
实现class和模块;
重要的特性:
块级作用域:ES5 中只有全局作用域和函数作用域,块级作用域的好处是不在需要立即执行的函数表达式,循环体中,
rest参数:用于获取函数的多余参数,这样就不需要使用arguments对象。
Promise:一种异步编程的解决方案,
模块化:主要有两个命令构成:export、import、
十九、DOM
文档对象模型,W3C组织推荐的处理的可扩展标志语言的标准编程接口;在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为DOM。
DOM中的API有:节点创建API、页面修改API、节点查询API、节点关系API、元素属性API、元素样式API、
二十、数组
1、数组扁平化处理:扁平化是指将多为数组变为一个一维数组。
方法一:使用flat():arr.flat(Infinity)
方法二:利用正则:JSON.stringify(arr).replce(/[|]/g,’’).split(’,’);
正则改良:JSON.parse(’[’+JSON.stringify(arr).replce(/[|]/g,’’)+’]’);
方法五:使用递归:
let newarr = [ ]
const fn = arr =>{
for(let i = 0;i<arr.length ;i++){
if(Array.isArray(arr[i])){
fn(arr[i])
}else{
newarr.push(arr[i])
} } }
fn(arr);
方法三:使用reduce
const flatten = arr =>{
return arr.reduce(
(pre,cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur):cur);
},[])
}
flatten(arr);
2、reduce讲解(没有听懂 )
(1)arr.reduce(function(prev,cur,index,arr){…},init):
arr表示原数组;prev表示上一次调用回调函数的返回值,或者初始值init(若没有初始值则变数数组的第一项);
cur表示当前正在处理的数组元素;
index表示当前正在处理的数组元素的索引,若提供init值则索引为0,否则为1;
init表示初始值。常用的为prev和cur.
(2)可以求和,求积,每个元素出现的个数,数组去重
3、数组的include方法:if(arr.includes(item)){console.log(item)} arr是定以的数组。
4、提前退出和提前返回:(代替多if-else )
const aaa = ({A,B,C}={})=>{ //对象解构
if(!A)return'no A'
if(!B)return'no B'
if(!C)return'no C'
return '${A} is ${B}${C}'
}
console.log(aaa({A:'1'B:'2',C:'3'})) //1 is 23
5、对象字面量代替switch语句
const fruitcolor ={
red:['apple']
yellow:['banana']
}
function fruit(color){
return fruitcolr[color] || []
}
6、Array.some Array.every
7、数组默认值 不能写:
list:{
type:Array,
// default:[] //会报错
default:()=>[] //正确写法
}
8、数组去重
法一:indexOf循环去重 法二:ES6 Set去重;Array.from(new Set(array)) 法三:Object 键值对去重;把数组的值存成 Object 的 key 值,比如 Object[value1] = true,在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的。
二十一、call apply bind
1、改变函数执行时的上下文,也就是改变this的指向。
通过apply和call改变函数的this指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply是数组,而call则是arg1,arg2…这种形式。通过bind改变this作用域会返回一个新的函数,这个函数不会马上执行
2、应用:
(1)把伪数组(dom节点、arguments、)转化为数组:Array.ptototype.slice.csll(伪数组)
(2)数组拼接:Array.ptototype.push.apply(arr1,arr2)
(3)判断数据类型:
二十二、瀑布流布局
定位后确定浏览器显示区域内,一行能放多少列图片盒子。
获取页面宽度–>获取图片盒子宽度–>计算可以显示的列数。
先排第一行,再获取所有盒子高度,第二行第一个会放在第一行中高度 最小的那一个的下方,改变最小列的高度,以此循环
加载图片的时机怎么把握