目录
Object.defineProperty(obj,prop,descriptor)方法
webpack的bundle、chunk、module分别是什么
nth-child(n)和nth-of-type(n)的区别
JavaScript
-
JS的数据类型
- 值类型:null、undefined、string、number、bollean、symbol
- 引用类型:object、function、array
- 两种类型的区别:
- 值类型存储在栈中。引用类型的变量名存储在栈中,值存储在堆中,且栈中存的是值的地址,该地址指向堆中的值。
- 值类型在赋值后,两个变量是相互独立的。引用类型赋值后,两个变量指向的是堆内存中的同一个值,修改其中一个,都会影响另一个。
-
JS中的深拷贝
- 注意:数组的concat方法和slice方法并不是深拷贝。它们只能做到数组的一级属性的深拷贝,再往下级,依然是浅拷贝。
- 递归拷贝:使用for in 去遍历对象的每一个元素,将其添加到新的空对象中,如果元素是对象,就递归进行拷贝。代码如下:
function deepCopy(obj) { //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝 var newObj= Array.isArray(obj) ? [] : {}; //进行深拷贝的不能为空,并且是对象 if (obj && typeof obj === "object") { for (key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] && typeof obj[key] === "object") { newObj[key] = deepCopy(obj[key]); } else { newObj[key] = obj[key]; } } } } return newObj; }
- Object.assign()方法:使用Object.assign(newObject,oldObj)方法,只能进行浅拷贝。如需进行深拷贝,还得递归使用此方法。代码如下:
function deepCopy(obj){ let newObj = new Object(); Object.assign(newObj,obj); for(let key in obj){ if(typeof obj[key] == 'object'){ newObj[key] = deepCopy(obj[key]); } } return newObj; }
- 使用JSON的方法:先使用JSON.stringify()将对象转换为JSON字符串,再用JSON.parse()将JSON字符串转换为对象,此对象已经与原对象脱离了关系,最后返回这个对象。但是这种简单粗暴的方法有其局限性:当值为undefined、function、symbol的时候,会在转换过程中被忽略。
-
null与undefined的区别
null表示已经赋值,但是赋值为空。undefined表示声明了但还未赋值。
-
null为什么是对象
JS存储变量时,将其机器码的低位的前三位存储数据类型,而null的机器码全是0,而JS把000视为对象。undefined的机器码是-2^30,typeof undefined == undefined。
-
使用new操作符后有哪些操作
- 创建一个新的空对象。
- 把对象的__proto__属性指向构造函数的prototype属性。
- 将构造函数中的this指向空对象。
- 执行构造函数中的代码。
- 返回这个对象。
-
this指向
- 对于普通函数,谁调用它,this就指向谁,一般都指向window。
- 对于构造函数,this指向的是new之后的实例对象。
- 对于箭头函数,它会捕获其定义位置的上下文的this,作为自己的this。
- 多层函数嵌套时,内层函数的this一般会指向window,若想让它指向父级,则需要用变量去保存this或使用箭头函数。
-
call、apply、bind区别与联系
- 联系:三者都用于改变函数中this的指向。都是函数的内置方法。
- call:接收多个参数,第一个参数为运行函数的作用域,后续参数作为默认参数依次传入调用函数。
- apply:接收两个参数,第一个参数与call一样为运行函数的作用域,第二个参数为一个数组或类数组作为参数传入调用函数。
- bind:接收多个参数,与call一样,第一个参数为运行函数的作用域,后续参数作为默认参数依次传入调用函数。但它与前两个方法的最大区别是bind方法会创建并返回一个函数,且该函数的 this 指向会指向传入的参数的作用域。
-
eval方法
eval()方法会把接收的字符串解析成JS代码并运行。尽量不要使用它,不安全,且十分消耗性能。
-
JSON
一种轻量级的数据交换格式,数据格式简单,易于读写,占用带宽小。采用键值对的方式表示。JSON.stringify()将对象字符串化,JSON.parse()将字符串解析为对象。
-
JS的内置对象
- 数据封装类对象:object、array、number、string、boolean
- 其他对象:function、date、math、arguments、regexp、error
- es6新增:set、map、promise、proxy、reflect、symbol
-
JS中数据类型的判断
- typeof:使用typeof来区分数据类型时,对于string、number、boolean、undefined、function、symbol,都可以返回相应的类型。但是在检测null、array、object等类型时,全都返回object。
- instanceof:A instanceof B 表示 A 是否为 B 的实例,抽象来说就是B的prototype是否在A的原型链上。返回true或false。注意:当A的原型链上只有null的时候,instanceof会失真。且instanceof仅适用于对象。
- constructor属性:constructor 是实例的属性,返回对创建此对象的构造函数,根据元素的constructor属性,可以判断对象的类型。但是null和undefined没有constructor属性。constructor属性是可修改的。
- toString():Object的原型方法,它返回当前对象的[[class]],这是一个内部属性,包含了对象的类型。对于非Object类型,需要借助call方法去调用(Object.prototype.toString.call()),从返回值中可以提取数据类型。
-
JS中对象的创建
- 工厂模式:声明一个创建函数,在创建函数中,创建一个新对象,给新对象添加相应的属性和方法,最终返回对象。这种创建方法不能解决对象识别问题。
- 构造函数模式:声明一个构造函数,内部使用this关键字,初始化对象的属性和方法。这种方法不能做到函数的复用。
- 原型模式:声明一个函数,内部不进行操作。直接修改函数的prototype属性,把属性和方法都添加到原型上。这种方法会导致每个实例对象都共享所有的属性,尤其是引用类型的属性。注意:若直接重写构造函数的原型,需要将其construcor重新指向构造函数。
- 组合使用构造函数模式和原型模式(推荐):使用构造函数模式添加实例属性,使用原型模式添加共享属性和方法。
-
原型和原型链
- 原型:
- __proto__(注意这里是两个_):实例对象的内置属性。它指向构造函数的prototype属性。IE不允许使用__proto__属性。
- prototype:函数的内置属性。它是一个指针,指向原型对象。用来修改和访问函数的原型。
- 注意点:__proto__是对象的属性,而prototype是函数的属性。
- 原型链:
- 当调用不在对象中直接定义的属性或方法时,会沿着其__proto__属性,向上查找,一直到找到Object.prototype为止,这样就形成了一条原型链。
- 原型链的顶端是null,因为每个对象的原型链的顶部都是Object.prototype,而Object.prototype的__proto__指向null。
- JS中的对象是通过引用传递的,如果修改了原型,则与之牵扯的对象都会有改变。
-
JS继承
- 原型链继承:将子类构造函数的prototype属性指向父类的实例。注意:重写prototype时,要改变constructor的指向。缺点:a.引用类型的属性会被所有子类的实例共享。b.创建子类实例时,无法向父类传参。
- 借用构造函数继承:在子类构造函数中,使用call或apply方法,将父类构造函数绑定在子类构造函数上。缺点:a.无法做到函数的复用。b.无法继承定义在父类构造函数原型上的属性和方法。
- 组合继承:将原型链继承和借用构造函数继承的方法结合,使用原型链继承,可以避免函数的多次创建。使用借用构造函数继承,可以避免所有实例共享引用类型的属性,且可以向父类传参。缺点:会调用两次父类构造函数。第一次是将子类构造函数的prototype指向父类构造函数实例时。第二次是在创建子类时,子类构造函数中绑定父类构造函数时。
- 原型式继承:创建一个新的构造函数,将构造函数的prototype指向已有的对象,最后返回这个构造函数的实例。原型式继承相当于对父类对象进行了一次浅复制。ES5的Object.create()方法规范了这种继承,可以直接拿来使用。缺点:所有子类实例会共享引用类型的值。
- 寄生式继承:创建一个仅用于封装继承过程的函数,该函数在内部用某种方式来增强对象,最后再返回新的对象。缺点:不能做到函数的复用。
- 寄生组合式继承(最理想的继承方式):主要是用来解决组合继承的缺点。仍然使用借用构造函数的方法去继承父类属性,但是在继承父类原型上定义的属性和方法时,不直接将子类的prototype指向父类实例,而是将使用寄生式继承的方法,创建一个新的构造函数,将其prototype指向父类构造函数的prototype,然后将其constructor指向子类构造函数,最后将子类的prototype指向新的构造函数的实例。这样就避免了组合继承的缺点,整体上只会调用一次父类的构造函数。缺点:麻烦。
- ES6中的extends关键字:具体内容查看阮一峰的博客:ES6 入门教程
-
作用域链、闭包、立即执行函数
- 作用域链:每个函数都有自己的作用域,函数嵌套形成作用域链,全局作用域是最外层的作用域,即作用域链的顶端是全局作用域。当函数中的所有代码执行完之后,该作用域就会被销毁,这就是局部作用域。全局作用域直到网页关闭或程序退出后才被销毁。内部环境可以通过作用域链来访问外部环境的属性和方法,外部无法访问到内部。
- 闭包:能够访问其他函数内部变量的函数。闭包可以理解为定义在一个函数内部的函数,它是将外部函数和内部函数连接起来的一座桥梁。
- 闭包的作用:
- 闭包可以用来修改和访问外部函数的内部变量。
- 闭包中的参数和变量不会被垃圾回收机制所回收。
- 立即执行函数:不必去调用,自动就可以执行的函数,在一次执行完之后,立即被释放,一般用于初始化工作,常常与闭包配合使用。
-
window对象和document对象
- window对象:指当前浏览器窗口,是JS的顶级对象。
- document对象:HTML文档,是window对象的一部分,通过window.document可以访问。
-
生成随机数公式
Math.floor(Math.random() * 可能值的个数 + 最小值)
-
有关数字的方法
- Math.ceil(x):向上取整。
- Math.floor(x):向下取整。
- Math.random():随机数(0~1)。
- Math.round(x):四舍五入。
- Math.max(x,y):返回较大值。
- Math.min(x,y):返回较小值。
- Math.pow(x,y):返回x的y次幂。
- Math.sqrt(x):返回x的平方根。
- Math.abs(x):返回x的绝对值。
-
有关数组的常用方法(注意:红色标注的方法会改变原数组)
- Array.isArray(x):检测x是否为数组。
- join():用指定的分割符分割数组元素,返回一个字符串。
- toString():将数组字符串化。
- filter():过滤数组。接收一个函数为参数,返回一个满足过滤条件的数组。
- every():接收一个函数为参数,如果数组的所有元素都满足条件,则返回true,否则返回false。
- some():接收一个函数为参数,如果数组中有一个元素满足条件,则返回true,否则返回false。
- map():映射方法。接收一个函数为参数,对数组的每个元素都运行给定函数,返回每次函数返回结果组成的数组。
- forEach():遍历方法。接收一个函数为参数,对数组进行遍历操作。
- reduce():归并方法。接收两个参数,第一个参数为归并函数,第二个参数是一个可选的初始值。
- reduceRight():与reduce一样,只不过是反方向遍历。
- indexOf():查找元素。接收两个参数,第一个为要查找的元素,第二个是一个可选的起始查找位置。找到则返回元素的所在位置,找不到返回-1。
- lastIndexOf():反向查找元素。
- concat():这个方法会先创建一个当前数组的副本,如果传入了参数,则会将参数依次添加到副本的末尾,并返回副本数组。如果没有参数,则直接返回副本数组,属于浅复制。
- slice():截取数组。接收两个参数,第一个是起始位置,第二个是可选的结束位置。如果省略第二个参数,则一直截取到数组末尾。注意:返回的数组不包括结束位置的元素。
- splice():可实现数组的删除、插入、替换。接收两个()及以上的参数。第一个参数为操作的起始位置,第二个参数为要删除的元素个数,后续传参为要插入的元素。注意:此方法返回的是被删除的元素组成的数组,而不是删除后的数组。
- sort():数组元素排序。接收一个可选的比较函数。没有参数则默认按升序排序。
- reverse():反转数组元素。
- push():接收多个参数,将他们依次添加到数组末尾,返回修改后的数组长度。
- pop():将数组的最后一个元素弹出(删除)。返回弹出元素。
- unshift():接收多个参数,将他们添加到数组的开头,返回修改后的数组长度。
- shift():将数组的第一个元素弹出(删除)。返回弹出元素。
- Array.from():ES6新增方法。接收一个类数组(arguments、Set等)为参数,将其转换为数组并返回。
-
有关字符串的方法(注意:下列方法都不会改变原字符串)
- charAt():接收一个位置(数字)为参数。返回该位置的字符。
- charCodeAt():参数与charAt()方法一样,但是返回的是该位置字符的字符编码。
- indexOf():查找字符,用法同数组。接收两个参数,第一个为要查找的字符(串),第二个是一个可选的起始查找位置。找到则返回字符(串)的所在位置,找不到返回-1。
- lastIndexOf():反向查找。
- concat():接收一个字符串为参数,将其拼接在原字符串之后,返回一个新的字符串。
- trim():去掉字符两端的空格。返回一个新的字符串。
- substr():截取字符串。接收两个参数,第一个为起始位置,第二个为可选的截取长度。
- substring():截取字符串。接收两个参数,第一个为起始位置,第二个为可选的结束位置。注意:不包含结束位置的字符。
- slice():截取字符串。用法与substring()方法相同。它们的区别在于两个方法在处理负数参数时不同(js-subString与slice差异_Zerofishcoding的博客-CSDN博客)。
- toUpperCase():将字符串转化为纯大写。返回一个新的字符串。
- toLowerCase():将字符串转化为纯小写。返回一个新的字符串。
- split():接收一个分割符(字符(串),可以为空字符串)为参数。将分割后的字符串 ( 一定注意:这里是字符串,即便是 数字 也会转换为字符串放入数组 ) 放入一个数组,并返回这个数组。注意:如果未传入参数,会将整个字符串当做一个元素放入数组中。
- replace():接收两个参数。第一个参数为要被替换的字符串或一个正则表达式,第二个为替换字符串或一个函数。注意:若的一个参数是一个字符串,则只会替换第一次出现的字符串。若想替换所有出现的字符串,必须使用正则。
- search():接收一个字符串或正则表达式为参数。返回其在原字符串中的位置,找不到返回-1。
- match():接收一个正则表达式为参数,将符合的字符串放入一个数组,并返回这个数组。
- includes():ES6新增方法。接收两个参数,第一个为一个字符串,第二个为一个可选的起始位置。包含则返回true,否则返回false。
- startswith():ES6新增方法。参数与includes方法相同。若参数出现在字符串的开始位置,就返回true,否则返回false。
- endswith():ES6新增方法。参数与includes方法相同。若参数出现在字符串的结束位置,就返回true,否则返回false。
- repeat():ES6新增方法。接收一个数字为参数。返回一个把原字符串重复指定次数后的新字符串。
-
setTimeout()和setInterval()
- setTimeout():在指定毫秒后,调用回调函数。返回一个定时器ID。
- setInterval():每隔指定毫秒后,调用回调函数。返回一个定时器ID。
- clearTimeout(id)和clearInterval(id):用来清除指定的定时器。
- 建议使用setTimeout()代替setInterval(),因为使用setInterval()时,后一个间歇调用可能在前一个间歇调用未执行完之前就开始。
-
JS的预解析
JS文件在正式开始执行之前,会将带有var和function声明的变量和函数在内存中排好,通过var声明的,不管是变量还是函数,它的值最初都是undefined。重名时,函数名优先级高于变量名。预解析是在程序进入一个新的环境时,把该环境里的变量或函数预解析到它们能调用的环境中。即每一次预解析的单位是一个执行环境。
-
事件流
- 冒泡型事件流(推荐):由IE提出。事件的传播从最特定的事件目标到最不特定的事件目标。
- 捕获型事件流:由Netspace提出。事件的传播是从最不特定的事件目标到最特定的事件目标。
- 标准的事件流(三个阶段):事件捕获阶段、处于目标阶段、事件冒泡阶段。
- focus和blur时间只有事件捕获阶段和处于目标阶段。
- 阻止冒泡和捕获进一步传播的方法:
- 旧版IE:window.event.cancelBubble = true
- 其他浏览器:event.stopPropagation()
- 取消默认事件的方法:
- 旧版IE:window.event.returnValue = false
- 其他浏览器:event.preventDefault()
-
事件委托
- 事件委托是值将事件绑定到目标元素的父级或更高级元素上,利用冒泡机制触发事件。
- 优点:
- 减少事件的注册,节省内存。
- 添加子元素时,无需再重复添加事件绑定。
-
JS的垃圾回收机制
- 标记清除:大部分浏览器所使用的机制。当变量进入执行环境时,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候,将其标记为“离开环境”。垃圾回收器在运行时会给内存中所有变量都加上标记(任意方式),然后去掉处在环境中的变量以及被环境中的变量引用的变量标记。而在此之后剩下的带有标记的变量被视为要删除的变量。
- 引用计数:低版本的IE所使用的机制。当声明一个变量并将一个引用类型赋值给该变量的时候,这个变量的引用次数加一,当此变量又取得了另一个值时,其引用次数减一,当引用次数为0的时候,就可以回收了。但是这种回收机制有一个缺点:循环引用的时候,引用次数无法变为0,也就无法回收,导致内存泄漏,必须得手动解除引用才行。
-
DOM操作
- creatElement():创建一个具体的元素。
- creatTextNode():创建一个文本节点。
- appendChild():添加子元素。
- removeChild():删除子元素。
- replaceChid():替换子元素。
- insertBefore():插入元素。
- getElementById():返回指定ID元素。
- getElementsByTagName():返回指定标签名的所有节点。(返回一个类数组)
- getElementsByClassName():返回指定类名的所有节点。(返回一个类数组)
- getAttribute():返回指定属性值。
- setAttribute():修改指定属性值。
-
Object.defineProperty(obj,prop,descriptor)方法
- 对象属性的四大特性:
- value:值。
- configurable:是否可配置(不可逆)。
- wirtable:是否可写。
- enumerable:是否可枚举。
- Object.defineProperty():用于修改现有对象属性的特性。如果属性不存在,则添加该属性。
- Object.defineProperties():修改多个对象属性的特性。
- 注意:不是用Object.defineProperties()定义的对象属性,三大特性的默认值都是true,而用Object.defineProperties()定义的对象属性,三大特性的默认值都是false。value特性的默认值始终是undefined。
- Object.getOwnPropertyDescriptor(obj,prop):返回指定对象属性的描述符。
-
EventLoop事件循环机制
- 同步任务:在主线程上排队执行的任务,只有前一个任务执行完之后,才能执行下一个任务。
- 异步任务:不进入主线程,而是进入一个“任务队列”,只有等待主线程上的任务全部执行完毕后,“任务队列”才去通知主线程,请求执行任务,然后才能进入主线程执行。
- 异步任务的分类:分为宏任务和微任务。先执行微任务,后执行宏任务。
- 宏任务:setTimeOut()、setInterval()
- 微任务:Promise.then()、process.nextTick()
- 事件执行顺序:同步事件进入主线程,异步事件的回调函数进入任务队列(宏任务和微任务有不同的任务队列)等待主线程任务执行完毕,从任务队列中去读取事件放入主线程执行(先执行微任务,后执行宏任务),然后循环上述操作。
-
forEach、for...in、for...of的区别
- forEach:
- 用于循环遍历数组。不可以使用break或return进行中断处理。
- 使用形式:arr.forEach((value,index,arr)=>{ ... })。
- for...in:
- 用于循环对象,也可以循环数组(不推荐),输出的顺序不定。
- 使用形式:for (let key in obj){ ... }
- for...of:
- 用于循环数组、类数组、字符串、Set、Map等,不能循环对象,可以进行中断处理。
- 使用形式:for(let value of arr){ ... }、for(let letter of str){ ... }、for(let [key,value] of map){ ... }
-
最简单的数组去重方法(可深度去重)
function quchong(arr){
let set = new Set(arr.map(e=>JSON.stringify(e)));
let transferArr = [...set]
let returnArr = transferArr.map(e=>JSON.parse(e))
return returnArr;
}
-
退出循环的三种方法
- continue:退出当前循环,进行下一次循环。
- break:退出整个循环。
- return:只能出现在函数体内。如果在函数体内的循环语句中出现,则退出整个循环,并函数体内语句的执行。
-
keydown、keyup 和 keypress的区别
- keypress 只能捕获单个字符。而 keydown 和 keyup 可以捕获组合键。
- keypress 将每个字符的大、小写形式作为不同的键代码解释,即作为两种不同的字符(区分大小写)。keydown 、keyup 不能判断键值字母的大小写。
- keypress 不区分小键盘和主键盘的数字字符。keydown 、keyup 区分小键盘和主键盘的数字字符。
- 当键盘一直处于按下状态时,keypress、keydown会一直触发。
-
JS处理浮点数的精度问题
- JS中 0.1 + 0.2 = 0.30000000000000004:由于计算机只能识别二进制数(0,1),因此在进行数值计算时,会先将数值转换为二进制数,再进行运算。0.1 => 0.0001 1001 1001 1001…(无限循环),0.2 => 0.0011 0011 0011 0011…(无限循环)。对于无限循环的小数,计算机会进行舍入处理。进行双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串二进制数字:0.0100110011001100110011001100110011001100110011001100 因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004。
- 解决方法:把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂)。
-
JS中整数的范围
- 范围:-Math.pow(2,53) ~ Math.pow(2,53)。超出这个范围的整数精度就不准确了。
- 判断:Number.isSafeInteger(x)用来判断一个整数(浮点数无论多大都返回false)是否落在这个范围之内。注意:Number.isSafeInteger(Math.pow(2,53))和Number.isSafeInteger(-Math.pow(2,53)) 均为 false。
-
JS声明二进制、八进制、十六进制
- 二进制:0b或0B开头。例:let x = 0b11
- 八进制:0o或0O开头。例:let x = 0o77
- 十六进制:0x或0X开头。例:let x = 0xff
-
DOM元素的 offsetTop / offsetLeft 、 clientTop / clientLeft 和 clientHeight / clientWidth属性
- offsetTop / offsetLeft:用来获取当前元素的 上/左 边框 相对于定位容器(position不为static)的位置。注意:如果所有祖先元素都是静态定位(position为static),则表示与文档最 上/左 方的距离。
- clientTop / clientLeft:不要被名字误导。clientTop / clientLeft 是指当前元素的 上/左 边框的宽度 的 整数值(浮点数会进行标准 四舍五入)。
- clientHeight / clientWidth:指当前元素的 高度 / 宽度(包括content和padding,不包括border) 的 整数值(浮点数会进行标准 四舍五入)。
-
JS的鼠标位置属性 screenX / screenY 和 clientX / clientY
- screenX / screenY:表示当前鼠标相对于 显示器(屏幕)的位置。
- clientX / clientY:表示当前鼠标相对于 浏览器窗口(文档)的位置。
-
滚动条位置属性 window.scrollX / window.scrollY
window.scrollX / window.scrollY指全局滚动条距离文档 左侧和顶部 位置。
-
JS防抖、节流
- 防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。代码如下:
function debounce(func,wait){ let timeId; return function(){ let that = this; let args = arguments; if(timeId){ clearTimeout(timeId) } timeId= setTimeout(() => { func.apply(that,args) }, wait); } }
- 节流:一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。代码如下:
function throttle(func,wait){ let pre = 0; return function(){ let that = this; let args = arguments; let now = Date.now(); if(now - pre > wait){ func.apply(that,args); pre = now; } } }
- 应用场景:
- 用户输入搜索时,在不断输入值时,用防抖来节约请求资源。停止输入后才搜索。
- 页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求。这个可以使用节流技术来实现。
-
JS的getter 和 setter
- 对象属性是由名字、值和一组特性构成的。在ES5中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter。由getter和setter定义的属性称做 "存储器属性",它不同于 "数据属性",数据属性就是一个简单的值。
- getter:当程序查询存取器属性的值时,JS调用 getter方法(无参数)。这个方法的返回值就是属性存取表达式的值。
- setter:当程序设置一个存取器属性的值时,JS调用 setter方法(有参数),将赋值表达式右侧的值当做参数传入setter。从某种意义上讲,这个方法负责 "设置"属性值。JS会忽略setter方法的返回值(即忽略return)。
- 和数据属性不同,存取器属性本身不具有可写性(即无法给自身赋值)。
- 如果属性同时具有getter和setter,那么它是一个读/写属性。
- 如果它只有getter方法,那么它是一个只读属性。
- 如果它只有setter方法,那么它是一个只写属性(数据属性中有一些例外)。读取只写属性总是返回undefined.
- 代码示例:
let obj = { _name: 'wang', get name(){ return this._name; }, set name(arg){ this._name = arg; } } obj.name = 'zhang'; console.log(obj.name); // zhang
-
函数的内置属性和方法
- 属性:
- name:返回本函数的函数名。
- length:返回本函数参数个数(使用ES6的rest参数时,length会失真)。
- prototype:这个属性指向一个对象的引用,这个对象称做原型对象(prototype object)。每一个函数都包含不同的原型对象。将函数用做构造函数时,新创建的对象会从原型对象上继承属性。
- caller(废弃):指向调用当前函数的函数。若无调用函数,则为null。
- 方法:
- call:用于改变this指向。
- apply:用于改变this指向。
- bind:用于改变this指向。会返回一个函数。
-
函数的arguments对象
- arguments对象是所有(非箭头)函数中都可用的局部变量。可以使用
arguments
对象在函数中引用函数的参数。此对象包含传递给函数的每个参数(注意:此处的参数是实参,不是形参)。 - arguments对象不是一个数组。它是一个类数组,但除了length属性和索引元素之外没有任何数组属性。
- arguments.length:返回传递给当前函数的参数数量(实参可能与形参个数不同)。
- arguments.callee:指向当前执行的函数。(建议总是使用这种方式调用函数自身,而不是使用函数名调用自身。)
-
JS中的URL编码(解码)方法:
- escape() 和 unescape():对除ASCII字母、数字、标点符号 @ * _ + - . / 以外的其他字符进行编码。
- encodeURI() 和 decodeURI():Javascript中真正用来对URL编码(解码)的函数,用于对整个URL进行编码。返回编码为有效的统一资源标识符 (URI) 的字符串,不会被编码的字符有 ! @ # $ & * ( ) = : / ; ? + '
- encodeURIComponent() 和 decodeURIComponent():对URL的组成部分进行个别编码,而不用于对整个URL进行编码。他会编码 ; / ? : @ & = + $ , # 这些特殊字符。例如 想要传递带&符号的网址,就要用encodeURIComponent()编码。
-
JS的事件
- UI事件:
- load:会在window, object 以及 img上面触发(注意:IE8和IE8之前的script标签不支持load, 但是支持onreadystatechange, IE家族中所有元素都支持这个状态属性)。
- unload:window.onunload、 object.onunload ,图像不触发onunload。
- resize: 把窗口拉大拉小, 最大化和最小化会触发这个事件(在移动端上的onoritatinochange反应很慢,就可以用resize代替),而且火狐的低版本是等到用户停止resize才会执行事件函数。
- scroll:滚动事件,主要是window.onscroll这个事件。
- error:当一个js代码执行发生错误的时候触发,或者img, object, script等需要请求远程资源的标签没有请求到, 就会触发这个事件, 这个事件实际工作中用的不多。
- abort:用户停止下载(用户问题)或者内容没有加载完毕(服务器问题), 就会触发。
- 焦点事件:
- focus:获取焦点的时候触发,不冒泡。
- blur:失去焦点的时候触发,不冒泡。
- focusin:冒泡的获取焦点事件。
- focusout:冒泡的失去焦点事件;
- 鼠标和滚轮事件:
- click:一般是点击左键触发这个事件,点击右键是触发右键菜单。如果当前的元素获得焦点,那么我们按回车(enter)也会触发click事件。
- dblclick:鼠标双击的时候触发、 如果dblclick触发了也会触发click的事件;
- mousedown:鼠标(左键或者右键)按下时触发。不能通过键盘触发。
- mouseup:鼠标(左键或者右键)被释放弹起时触发。不能通过键盘触发。
- mousemove:鼠标在元素内部移动时不断触发。不能通过键盘触发。
- mouseover:鼠标移入目标元素上方时触发。鼠标移到其后代元素上时会触发(该事件支持冒泡)。
- mouseout:鼠标移出目标元素上方时触发(该事件支持冒泡)。
- mouseenter:鼠标移入元素范围内触发(该事件不冒泡)。
- mouseleave:鼠标移出元素范围时触发(该事件不冒泡)。
- mousewheel:鼠标滚轮在垂直方向上滚动页面时(无论向上还是向下)触发。
- 键盘事件:
- keypress:键盘按键按下时触发。(如果用户按住不放会重复触发,而且这个键有点延迟。)
- keydown:键盘按键按下时触发。(如果用户按住不放会重复触发,而且这个键有点延迟。)
- keyup:键盘按键弹起时触发。
- 变动事件:
- input:该事件在 <input> 或 <textarea> 元素的值发生改变时立即触发。然而通过 JS 改变 value 时,不会触发此事件。该事件类似于 change 事件,但不同。
- change:该事件在内容改变(两次内容有可能还是相等的)且失去焦点时触发。
-
JS的隐式转换
- 隐式转换:在JS中,当运算符在运算时,如果两边数据不统一,CPU就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算。 这种无需程序员手动转换,而由编译器自动转换的方式就称为隐式转换。
- 转成string类型: +(字符串连接符)
- 转成number类型:++、--(自增自减运算符) +、-、 *、 / 、%(算术运算符) >、<、>=、<=、==、!=、===、!=== (关系运算符)
- 转成boolean类型:!(逻辑非运算符)
- JS中的不同的数据类型之间的比较转换规则如下:
- 字符串和字符串进行比较时,全都转换为数字然后进行比较。注意:此时并不是按照Number()的形式转换为数字,而是按照字符串对应的Unicode编码来转成数字。
- 对象和布尔值进行比较时,对象先转换为字符串,然后再转换为数字,布尔值直接转换为数字,然后两者进行比较。
- 对象和字符串进行比较时,对象转换为字符串,然后两者进行比较。
- 对象和数字进行比较时,对象先转换为字符串,然后再转换为数字,再和数字进行比较。
- 字符串和数字进行比较时,字符串转换成数字,二者再比较。
- 字符串和布尔值进行比较时,二者全部转换成数字再比较。
- 布尔值和数字进行比较时,布尔转换为数字,二者比较。
- 常见特殊的比较(下列比较结果均为 true):
- ![] 和 !{}都返回false
- NaN != NaN
- null == undefined
- [] == ![]
- [] !== []
- {} !== {}
- {} !== !{}
- ![] == !{}
- 常见面试题及解析:
-
常用正则表达式符号
-
JS懒加载(图片懒加载)
//获取图片列表
let imgs = document.getElementsByTagName('img');
// 方法一:(浏览器兼容性较差),但使用简便,不用绑定滚轮事件
let observer = new IntersectionObserver(lazyLoad);
[...imgs].forEach(e=>{
observer.observe(e);
})
function lazyLoad(arr){
arr.forEach(e=>{
if(e.intersectionRatio > 0){ //e.intersectionRatio可看做图片进入可视区域的比例。
// console.log(e.intersectionRatio)
e.target.src = e.target.getAttribute('data-src');
observer.unobserve(e.target);
}
})
}
//方法二:(浏览器兼容性较好),但使用复杂,需绑定滚轮时间
//先加载位于视口内的图片
for(let i = 0;i < imgs.length;i++){
let _loca = imgs[i].getBoundingClientRect(),
_height = Math.min(document.body.clientHeight,document.documentElement.clientHeight);
if(_loca.top - _height <= 0){
imgs[i].src = imgs[i].dataset.src;
}else{
break;
}
}
//监听滚轮
window.onscroll = throttle(lazyLoad(),200);
//懒加载
function lazyLoad(){
let height,loca,len;
let loadArr = [];
for(let i = 0;i < imgs.length;i++){
loadArr[i] = i;
}
return function(){
height = Math.min(document.body.clientHeight,document.documentElement.clientHeight);
len = loadArr.length;
if(len == 0){
return;
}
for(let i = 0;i < len;i++){
loca = imgs[loadArr[i]].getBoundingClientRect();
if(loca.top - height <= 0){
imgs[loadArr[i]].src = imgs[loadArr[i]].dataset.src;
loadArr.shift();
len--;
i--;
}else{
break;
}
}
}
}
//节流
function throttle(func,wait){
let pre = 0;
return function(){
let now = Date.now();
if(now - pre > wait){
func();
pre = now;
}
}
}
-
JS操作cookie
- 创建 / 修改cookie:使用 document.cookie 属性来创建 / 修改。例如:document.cookie="username=Jack"
- cookie过期时间( UTC 或 GMT 时间):默认情况下,cookie 在浏览器关闭时删除。可以使用 expires参数手动设置过期时间。例如:document.cookie="username=Jack; expires=Thu, 18 Dec 2043 12:00:00 GMT"
- cookie路径:默认情况下,cookie 属于当前页面。可以使用 path 参数告诉浏览器 cookie 的路径。例如:document.cookie="username=Jack; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=../"
- 删除 cookie:只需要设置 expires 参数为以前的时间即可删除cookie。注意:删除cookie时不必指定 cookie 的值。例如:document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT"
- document.cookie 属性看起来像一个普通的文本字符串,其实它不是。即使在 document.cookie 中写入一个完整的 cookie 字符串, 当重新读取该 cookie 信息时,cookie 信息是以名/值对的形式展示的。如果设置了新的 cookie,旧的 cookie 不会被覆盖。 新 cookie 将添加到 document.cookie 中。
- 封装cookie操作:
- 设置 cookie:
function setCookie(cname,cvalue,exdays) { var d = new Date(); d.setTime(d.getTime()+(exdays*24*60*60*1000)); var expires = "expires="+d.toGMTString(); document.cookie = cname + "=" + cvalue + "; " + expires; }
- 获取 cookie:
function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i=0; i<ca.length; i++) { var c = ca[i].trim(); if (c.indexOf(name)==0) return c.substring(name.length); } return ""; }
- 设置 cookie:
-
JS操作localstorage(注:下列操作中 storage = window.localStorage)
- 注意:localStorage只支持string类型的存储。即使存储的值是其它类型,取出后仍会转换为string类型。
- 写入操作:
- 推荐写法:例:storage.setItem("age",18);
- 写法二:例:storage.age = 18;
- 写法三:例:storage["age"] = 18;
- 读取操作:
- 推荐写法:例:storage.getItem("age");
- 写法二:例:storage.age;
- 写法三:例:storage["age"];
- 修改操作:同写操作一样,只要设置相同的键,即可覆盖之前的值。
- 删除操作:
- 删除localStorage的所有内容:storage.clear();
- 删除localStorage中的某个键值对:例:storage.removeItem(age);
- 遍历key值:
for(let i = 0;i < storage.length;i++){ let key = storage.key(i); console.log(key); }
- 为localstorage设置过期时间:
- 写入:
function set(key,value,expired){//此处 expired (过期时间)以天为单位,可以自行设置。 if(expired){ storage.setItem(key,JSON.stringify({ value: value, expired: Date.now() + 1000*60*60*24*expired })); }else{ storage.setItem(key,JSON.stringify({ value: value })); } }
- 读取:
function get(key){ let data = JSON.parse(storage.getItem(key)); let expired = data.expired || Date.now() + 10000;//如果没有设置expired(过期时间),则默认永久有效。 if(expired > Date.now()){ return data.value; } return undefined; }
- 写入:
-
e.target 和 e.currentTarget 的区别
e.target
:指向触发事件监听的对象。e.currentTarget
:指向添加监听事件的对象。
ES6
-
let、const
- let:声明一个变量,此变量不能通过 window.变量名 的方式访问到。在for循环中,使用let声明循环参数,每次迭代都会创建新的绑 定。
- const:用来声明一个常量,在声明时必须赋值,而且在之不能重新赋值。如果声明的是一个对象,那么对象内的属性值是可以进行修改的。
- const声明的对象中的属性可改变吗?为什么?
- 可以改变。
- const声明的引用类型的指针指向的地址不可以变化,但是指向地址的内容可以变化。
- let和const都具有块级作用域,且不存在变量提升。
- 块级作用域:ES5中作用域有:全局作用域、函数作用域。没有块作用域的概念。ES6中新增了块级作用域,
块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。 - let、const的暂时性死区:在代码块内,使用
let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
-
如何冻结一个对象(对象属性只可访问,不可修改、添加)
使用ES6提供的 Object.freeze(obj) 方法可进行对象的浅度冻结。若要深度冻结则需递归调用 Object.freeze(obj)方法。代码如下:
function freezeObj(obj){
Object.freeze(obj);
Object.keys(obj).forEach(key=>{
if(typeof obj[key] === 'object') {
freezeObj(obj[key]);
}
})
}
-
Symbol
es6新增类型,属于基本数据类型。Symbol值是通过Symbol函数生成的,每个值都是独一无二的。
-
模板字符串
- 使用反引号标识,内部使用${变量名}的方式引用变量,大括号内可以是任意JS表达式,可以进行计算、引用对象属性等操作。
- 使用字符串模板表示多行文字时,所有的空格和缩进都会保留在输出中。可以使用trim()方法进行去两头空格。
-
ES6中对象的扩展
- 声明对象时,当对象属性名与赋值变量名相等时,可以只写属性名。
- 声明对象时,对象中的方法可以直接写成方法名加括号的形式。
- Object.keys(obj):此方法可以获取对象中所有的属性名和方法名,返回一个数组。注意:此方法不能获取对象原型上的属性和方法。
- Object.assign():此方法用于将多个源对象的属性和方法合并到目标对象上。接收多个参数,第一个参数为目标对象,后续参数都是源对象,如有重名的属性或方法,则会被后出现的覆盖掉。
-
解构赋值
- 解构赋值简化了赋值操作:如x与y互换值,只需[x,y] = [y,x]一条语句即可。
- 数组的解构:数组的解构赋值位置要一一对应,对应不上就会赋值undefined。
- 对象的解构:对象的属性是无序的,变量名与属性名相同时,才会正确赋值,否则将赋值undefined。如果变量名与属性名不同,则应写成这样:{foo: baz} = {foo: "abc"},这样就会将 "abc" 赋值给 baz 。
- 解构赋值中,可使用 “=” 设置默认值,若匹配失败则会使用默认值。
- 应用(具体参考:http://es6.ruanyifeng.com/#docs/destructuring#用途):
- 交换变量的值。
- 从函数返回多个值。
- 提取 JSON 数据。
- 遍历 Map 结构。
-
模块化开发
- Node中的CommonJS:使用module.exports导出模块,使用require引入模块。一个文件就是一个模块,每个模块都有自己的作用域,它内部的变量、函数、类都是私有的。每个模块都有一个module对象,它的exports方法是对外的一个接口,用来导出模块。模块可以多次加载,但是只会在第一次加载时运行一次(动态加载),之后的加载会直接取出缓存使用。要想让模块再次运行,必须清除缓存。
- CommonJS模块的加载机制:导入的是导出的值的拷贝,一旦导出一个值,模块内部的变化就不会再影响到这个值了。
- ES6的模块化:使用export导出代码,使用import导入。ES6的模块不是对象。它的加载机制是编译时加载(静态加载),所以不能使用表达式和变量,因为运行时才知道具体的值,可以使用‘as’关键字进行重命名。
-
ES6中函数的扩展
- ES6中允许函数参数指定默认值,此时函数的length属性会失真,只会返回指定默认值之前的参数个数。
- 函数引入了rest参数,形式为 ...变量名 。用于获取多余的参数,这样便不需要arguments对象了。注意:rest参数是一个实实在在的数组,而arguments是一个类数组。此外,函数的length属性,不包括 rest 参数。
- 箭头函数:
- 只有一个参数时,可以省略小括号。函数体中只有一条语句时,可以省略大括号,且可以省略return关键字。
- 箭头函数的 this 会将定义时所在位置的上下文的 this 作为自身的this。而不是指向调用对象。
- 箭头函数的 this 指向不可改变。
- 不能用作构造函数,即不可使用new命令。
- 不能使用arguments对象。
- 不能使用yield命令,即不能用于Generator函数。
- 最好不要使用箭头函数的情况:5种应该避免使用箭头函数的情况 | Fundebug博客 - 一行代码搞定BUG监控 - 网站错误监控|JS错误监控|资源加载错误|网络请求错误|小程序错误监控|Java异常监控|监控报警|Source Map|用户行为|可视化重现
-
尾调用、尾递归
- 尾调用:某个函数的最后一步操作是调用一个函数。注意:此处是 最后一步操作 ,而不是 最后一行代码 ,只要是最后一步操作就行,不一定是最后一行代码。
- 尾调用优化:尾调用可大大节省内存。只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧。
- 尾递归:函数尾调用自身。普通递归需要同时保存成千上万个调用帧,容易发生栈溢出。而尾递归只会保留一个调用记录,不会发生栈溢出。
-
Generator函数
一个状态机,内部会使用yield操作符来定义状态,也就是在后面调用产出的东西。Generator函数返回一个遍历器对象,调用遍历器对象的next()方法产出不同的状态。遍历器有Iterator接口,next()中的参数会传递回状态机中,遍历器中的return会结束遍历器。使用 yield* 表示在一个Generator函数中调用另一个Generator函数。
-
扩展运算符
- 将数组或类数组对象展开成一系列用逗号隔开的值。注意:它与函数的 rest参数不同,作用刚好相反。
- 应用:
- 数组浅拷贝:例如:arr2 = [ ...arr ] 。注意:是浅拷贝,不是深拷贝。
- 把一个数组插入到另一个数组:例如:arr2 = [ 1,2,...arr,3,4 ]
-
Set数据结构
- Set本身是一个构造函数,类似数组。它里面的元素没有重复。
- 常用方法:
- add():用于添加元素,返回Set本身。
- delete(value):用于删除指定元素,返回true或false。
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回键值对的遍历器。
- 使用Set进行数组去重(浅度去重):实例代码:
const set = new Set([1,1,2,3]); let arr = [...set]; console.log(arr) // [1,2,3]
- WeakSet:
与Set有两点区别:- WeakSet的成员只能是对象。
- WeakSet中的对象都是弱引用,垃圾回收机制不考虑WeakSet对该对象的引用。
-
Map数据结构
- Map本身是一个构造函数,类似对象,也是键值对的集合,但它的键不限于字符串,可以是任何类型的值。
- 常用属性和方法:
- size:返回成员个数的属性。
- delete(key):删除指定元素,返回true或false。
- get(key):获取对应键值。
- set(key,value):添加或修改键值对,返回当前Map。
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
-
Class
- 使用class声明一个类时,内部必须有constructor方法,实例属性在constructor方法中定义,constructor中的this指向实例对象。class中的定义的方法相当于ES5中在构造函数的原型上定义的方法。注意:使用class定义的类中的方法是不可枚举的,而ES5中的原型上定义的方法可以枚举。且使用class定义的类是不存在变量提升的。
- 继承:子类通过extends关键字继承父类,它的constructor方法中,必须含有super()函数。super()表示调用父类的构造函数。
- 静态方法:class声明的类中,如果在方法前加上static,表示该方法是静态方法,是属于类本身的,而不是实例的。里面的this指向的也是类本身。静态方法可以被继承。
-
Proxy
在目标对象前架设一层拦截,外界访问或修改目标对象时,必须通过拦截。Proxy中定义了许多实例方法,用于外界访问或修改目标对象时,进行过滤、改写。常见的有get()、set()等方法。
-
Promise
- Promise是异步编程的一种方案。它有三个状态:pending、resolved、rejected。状态只会从 pending到resolved 或 pending到rejected 两种变化,且一旦状态发生改变,就固定了,不可再发生改变。Promise创建后会立即执行。
- Promise接收一个参数,该参数是一个函数,此函数有两个参数,分别是resolve方法和reject方法。注意:调用
resolve
或reject
并不会终结 Promise 的参数函数向下继续执行。 - Promise实例的then()方法接收两个参数,第一个是resolve方法,成功时调用,第二个是reject方法,失败时调用。then()方法返回一个Promise实例。
- Promise实例还有一个catch()方法,失败时调用,它可以捕获错误。catch()方法返回一个Promise实例。
- 建议总是将then()方法和catch()方法结合使用。
- Promise实例的finally()方法接收一个回调函数为参数。不管Promise实例最后的状态是什么,在执行完then或catch中指定的回调函数后,都会执行finally中指定的回调函数,这个回调函数不接收任何参数。
- Promise.all()将多个Promise实例包装成一个新的Promise实例。它们共同决定了新实例的状态。
- Promise.race()也将多个Promise实例包装成一个新的Promise实例。但是其中任意一个实例都可能决定新实例的状态。
-
async函数
- async 函数是什么?一句话,它就是 Generator 函数的语法糖。函async函数就是将 Generator 函数的星号(
*
)替换成async,将yield替换成await,仅此而已。 - 优点:
- 内置执行器:Generator 函数的执行必须靠执行器,所以才有了
co
模块,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数一模一样,只要一行。例:asyncReadFile(); - 更好的语义:
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。 - 更广的适用性:
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。 - 返回值是 Promise:
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then
方法指定下一步的操作。进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
- 内置执行器:Generator 函数的执行必须靠执行器,所以才有了
- 使用:
- 声明:async function foo() {};
- 执行:
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。 - await:正常情况下,
await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。任何一个await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。 - 错误处理:有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。
- 方法一:将第一个
await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。 - 方法二:在
await
后面的 Promise 对象再跟一个catch
方法,处理前面可能出现的错误。
- 方法一:将第一个
- 返回:
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。 - 状态变化:
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
Vue
-
vue的两大核心
- 数据驱动
- 组件系统
-
vue的生命周期(钩子函数)
- 首先使用new Vue()创建vue实例,之后执行init(初始化)。
- init的过程中首先调用beforeCreate钩子。然后进行injections(注射)和reactivity(反应性)的时候,调用created钩子。
- 判断instance(实例)中是否有 ‘el’ option(选项),若果有,继续向下进行,如果没有,则先调用vm.$mount(el)方法,再执行下一步。
- 接着判断是否有 ‘template’ option,有就把‘template’解析为一个 render(渲染)function。没有就使用挂载元素的outHTML来生成一个render function。
- 接着调用beforeMount钩子。
- 接着执行render function,执行完后,调用mounted钩子。
- 之后如果有数据更新,则先调用beforeUpdate钩子。
- 重新渲染DOM后,调用updated钩子。
- 组件被销毁时,先调用beforeDestroy钩子。
- 组件销毁后,调用destroyed钩子。
- 补充:在开发时,有一个renderError钩子,用于调试render中的错误。
-
vue的数据双向绑定
- 原理:首先vue会将data中的数据转换为getter和setter。每个组件在初始化的时候会生成一个Watcher实例。在挂载元素中使用了某一属性时,被使用的属性就会生成一个依赖(Dep实例),多个属性被使用就会生成多个依赖。Watcher实例会监听所有的依赖。当data中的属性发生改变时,会通知相应的依赖,依赖再去通知Watcher实例,然后Watcher实例会调用view层的接口去改变数据。当view层的数据发生变化时,会通知Watcher实例,Watcher实例通知相应的依赖,依赖再去调用setter修改data中的数据。
- 补充:对于基本类型的数据,经过操作后,值没有变化就不会触发re-render function,但是对于引用类型,只要有操作,就会触发re-render function 进行重绘。
-
computed和watch的区别
- computed适用于 一个数据受到多个数据的影响的情况下使用,watch适用于 一个数据影响多个数据的情况下使用。
- watch中可以进行一些复杂的操作,而computed只是值对值的依赖,不能进行复杂操作。
-
vue的页面传参
- 通过路由自带参数进行传参:
- 使用query传参:路由必须使用path引入,用this.$route.query获取。参数会暴露在url中。
- 使用params传参:路由必须使用name引入,用this.$route.params获取。参数不会暴露。
- 使用webStorage进行参数传递。
- 使用eventBus传参(适用于小项目):
- 在全局中定义一个eventBus:window.eventBus = new Vue()
- 在需要传参的组件中使用$emit()发送要传输的值:eventBus.$emit( key , value )
- 在需要接收参数的组件中使用$on()接收值:eventBus.$on( key , value=>{...} )
- 在接收参数的组件的beforeDestroy()钩子中使用$off()关闭这个eventBus:eventBus.$off(key)
- 使用vuex传参(适用于大项目)。
- 父子组件间的传值:
- 父组件向子组件传值:
- 在子组件中创建props,并添加要设置的特性。
- 在父组件中注册子组件,设置添加的自定义特性,给它赋值,便可以传到子组件了。
- 子组件向父组件传值:
- 子组件中需要定义一个自定义事件(以某种方式触发)。
- 在触发函数中调用this.$emit()方法,此方法有两个参数,第一个参数为自定义事件的名字,第二个参数是要传递给父组件的值。
- 在父组件中注册子组件,监听自定义事件,触发此事件的函数的默认参数就是子组件传来的值。
- 共同点:无论是子组件向父组件传值,还是父组件向子组件传值,都需要一个中间介质。子组件向父组件传值是利用自定义事件,父组件向子组件传值是利用自定义特性。
- 父组件向子组件传值:
-
vue的自定义组件
- 使用Vue.extend()方法创建组件,Vue.component()方法注入组件。若果要局部注册组件,则需要将组件添加到Vue实例中的components选项中。
- 组件名最好使用小写且用 ‘-’ 连接的方式命名。
- 将组件的HTML代码写入<template>中。
- 给组件设置自定义属性用props选项,将要设置的属性直接放入props选项中即可。
- 给组件设置自定义事件使用$emit()方法。
-
MVVM
- MVVM是Model-View-ViewModel的缩写。
- Model是数据模型,View是UI组件,ViewModel监听模型数据的改变和控制视图行为、处理用户交互。
- MVVM中,Model与View没有直接联系,而是通过ViewModel进行交互。
- ViewModel通过双向数据绑定把View与Model连接起来,而View和Model之间的同步工作也是完全自动的,不需要人为干涉。
-
Vue与Angular的异同
- 相同点:都支持指令、都支持过滤器、都支持双向数据绑定。
- 区别:Angular学习成本高,使用复杂。
-
Vue与React的异同
- 相同点:都是组件化的,都提供了合理的钩子函数,都广泛使用插件。
- 区别:React数据不可变,而Vue是基于可变的数据的。React是单向数据传输流,Vue是双向数据绑定。
-
vue路由中的hash模式和history模式
- hash模式:url中 ‘#’ 和 ‘#’ 后面的字符串成为hash,用window.location.hash读取。虽然hash在url中,但它不被包含在HTTP请求中。因此对于后端来说,即便是没有做到路由的全覆盖,也不会返回404错误。
- history模式:history模式采用了H5的新特性,且提供了两个方法:pushState()、replaceState(),可以对浏览器历史记录栈进行修改。history模式下,前端url与实际向后台发起请求的url一致。因此对于后端来说,如果没有做到路由的全覆盖,将可能会返回404错误。
-
vue的自定义指令
- 创建自定义指令:
- 注意:自定义指令名若为拼接单词,在定义时需要写成驼峰形式,例如:changeBackgroundColor。但在使用时需写成用 ‘v-’ 加用 ‘-’ 连接的纯小写形式,例如:v-change-background-color。
- 定义自定义指令时,有五个可选的钩子函数:bind、inserted、update、componentUpdate、unbind。
- bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下文)。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
- 钩子函数有如下的参数:
el
、binding
、vnode、oldVnode。
el
:指令所绑定的元素,可以用来直接操作 DOM 。binding
:一个对象,包含以下属性:name
:指令名,不包括 v- 前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue
编译生成的虚拟节点。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
- 创建局部自定义指令:在vue实例中的 directives选项中定义。
- 创建全局自定义指令:使用Vue.directive()方法创建。
-
vue的自定义过滤器
- 创建自定义过滤器:
- 过滤器是一个函数,函数默认参数是要过滤的值,还可接收参数,进行近一步的操作,最后返回过滤后的值。
- 使用过滤器时,只需在过滤值的后边加上 ‘ | 过滤器 ’ 即可。例如:不需要传参时: price | add , 需要传参时: price | add(args) 。 注:price 为要过滤的值,add为过滤器。
- 创建局部自定义过滤器:在vue实例中的 filters选项中定义。
- 创建全局自定义过滤器:使用Vue.filter()方法创建。
-
vue的组件缓存
- keep-alive:vue的一个内置组件,可以使被包含的组件保留状态,或避免重新渲染。它有两个属性:include、exclude(优先级大于include)。include中是要缓存的组件,exclude中是不被缓存的组件。include和exclude都可使用字符串、正则表达式和数组。字符串用 ‘ , ’ 分隔组件名。使用正则和数组时要使用 v-bind 指令绑定。
- 设置meta:结合router,缓存部分页面。在router中设置router的元信息meta中的keepAlive属性。如果此属性为true,则表示缓存该页面,为false,则表示不缓存该页面。设置好keepAlive属性后,在router-view 标签中,使用 v-if 绑定$route.meta.keepAlive的值即可。
- keep-alive有两个特有的钩子函数:activated、deactivated。在使用了keep-alive之后,钩子函数的触发顺序为: created -> mounted -> activated,退出页面触发 deactivated钩子。当再次前进、后退到达此页面时,只会触发activated,它承担了原来created钩子中获取数据的任务。
-
vue-router的导航钩子
- 全局导航钩子:
- 全局前置守卫:router.beforeEach((to,from,next)=>{ ... }) 。其中 to 是要进入的路由对象,from 是要离开的路由对象。next 是一个必须调用的方法,否则钩子函数无法resolved。next方法的参数,决定了执行效果:
- next():进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的)。
- next(false):这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from路由对应的地址。
- next('/') 和 next({path: '/'}):在中断掉当前导航的同时,跳转到一个不同的地址。
- next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调。
- 全局后置守卫:router.afterEach((to,from)=>{ ... }) 。后置守卫不同于前置守卫,它是没有 next方法的,也不会改变导航本身。
- 全局前置守卫:router.beforeEach((to,from,next)=>{ ... }) 。其中 to 是要进入的路由对象,from 是要离开的路由对象。next 是一个必须调用的方法,否则钩子函数无法resolved。next方法的参数,决定了执行效果:
- 路由独享的钩子:单个路由独享的导航钩子,它是在路由配置上直接进行定义的。有beforeEnter、beforeLeave等钩子函数。
- 组件内的钩子:组件内的导航钩子主要有三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他们是直接在路由组件内部直接进行定义的。注意:beforeRouterEnter钩子中不能获取组件实例this,因为此时组件实例还未创建。可以通过给它的 next 传入一个回调来访问组件实例。在导航被确认是,会执行这个回调,这时就可以访问组件实例了,如:
注意:仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持。因为归根结底,支持回调是为了解决 this 问题,而其他两个钩子的 this 可以正确访问到组件实例,所有没有必要使用回调beforeRouteEnter(to, from, next) { next (vm => { // 这里通过 vm 来访问组件实例解决了没有 this 的问题 }) }
-
v-if 和 v-show 的区别
- v-if 是惰性的,如果在初始渲染时条件为假,那么什么都不做。直到条件第一次为真的时候才会开始渲染条件块。相比之下,v-show就简单得多,不管初始条件是什么,元素总会被渲染,并且只是简单的基于css进行切换,即display是否为none。
- 一般来说,v-if 有更高的切换开销。因此,如果需要非常频繁的切换,那么使用v-show好一点。如果在运行时条件不太可能改变,则使用v-if 好点。
-
$route和$router的区别
- $route 是 路由信息对象 ,它包括了 path、query、name、params、hash、fullPath、matched、meta等路由信息参数。
- $router 是 路由实例对象 ,它包括了路由跳转的方法、钩子函数等。
-
vue常用指令
v-if、v-show、v-for、v-on、v-bind、v-model、v-once
-
vue中操作DOM
在DOM标签中添加 ref 属性并赋值。脚本中使用 $refs.refName 调用DOM。
-
vue-loader
.vue文件的一个加载器。用途:js可以写ES6,style可以写scss、less,template可以加jade等。
Webpack
-
webpack是什么,它与其他打包工具有什么区别
- webpack是一个模块打包器。
- 与其它工具的区别是,它支持code-splitting、模块化、全局分析。
-
webpack的bundle、chunk、module分别是什么
- bundle:webpack打包出来的文件。
- chunk:webpack在进行模块的依赖分析时分割出来的代码块。
- module:开发中的单个模块。
-
webpack的loader和plugin
- loader:用于加载某些资源文件。webpack自身只能够识别JS文件,对于其他的资源,如css、img等,是没有办法加载的。此时就需要对应的loader将资源转化为JS文件,从而进行加载。loader需要在配置文件(webpack.config.js)中单独使用module进行配置。常用的loader有babel-loader、css-loader、style-loader等。
- plugin:用于扩展webpack的功能。它不同于loader。它的功能更加丰富,可进行压缩打包、优化等,不限于资源的加载。
-
webpack的模块热更新
在代码修改后,不用刷新浏览器就可以更新。在添加、删除模块时,无需加载整个页面。是高级版的自动刷新浏览器。
-
webpack的Tree-shaking
指在打包时去除无用的代码。
HTML
-
HTML 的严格模式和混杂模式
- 严格模式:又称标准模式,是指浏览器按照W3C标准来解析代码,一种严格要求的DTD,排版和JS运作模式均是以该浏览器支持的最高标准运行。
- 混杂模式:又称怪异模式或者兼容模式,是指浏览器按照自己的方式来解析代码,页面以宽松的向后兼容的方式显示,就严格度上来说不如严格模式,但是模拟老式浏览器的行为可以防止站点无法工作。
- 如何区分严格模式和混杂模式:
- 如果文档中包含了严格的DOCTYPE,那么它一般以严格模式呈现。
- 如果文档中包含过渡DTD和URI的DOCTYPE,也以严格模式呈现。但有过渡DTD而没有URI,会导致文档以混杂模式呈现。
- DOCTYPE不存在或者形式不正确或有误,文档以混杂模式呈现。
- HTML5没有DTD,因此也就没有严格模式与混杂模式的区分,HTML5相对来说语法比较宽松。
- 严格模式和混杂模式的区别:
- 混杂模式中盒模型的宽高包含内容content、内边距padding和边框border。而严格模式只包含内容content。
- 混杂模式可以设置行内元素的宽高。而严格模式不能设置。
- margin:0 auto设置水平居中在IE下会失效。解决办法是用text-align。
- 混杂模式中即使父元素没有指定宽高,子元素也可设置百分比的宽高。而在严格模式下,一个元素的宽高是由它包含的内容来决定的,如果父元素没有设置宽高,子元素设置一个百分比的宽高是无效的。
- 混杂模式模式下设置图片的padding会失效。
- 混杂模式table的自身属性不能继承上层的设置。
- 混杂模式white-space:pre会失效。
-
<script>标签
- 位置:最好将script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
- defer 和 async 属性:
- 若没有 defer或 async属性,浏览器会立即加载并执行相应的脚本。也就是说在渲染script标签之后的文档之前,不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载。
- 有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行。
- 有了defer属性,加载后续文档的过程和js脚本的加载(此时仅加载不执行)是并行进行的(异步),js脚本的执行需要等到文档所有元素解析完成之后,DOMContentLoaded事件触发执行之前。
- defer 和 async的区别:如果存在多个有defer属性的脚本,那么它们是按照出现顺序执行脚本的。而对于async,它的加载和执行是紧紧挨着的,无论声明顺序如何,只要加载完成就立刻执行,它对于应用脚本用处不大,因为它完全不考虑依赖。
- 在现实使用当中,使用了defer属性的延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoad事件触发前执行,因此最好只包含一个延迟脚本。
- 有外链接(有src属性)的 script标签中的代码会不会执行:不会执行。
CSS
-
居中方法
- 水平居中:
- 内联元素可设置 text-align 属性为 center 进行居中。
- 给块级元素添加 display: inline-block,再给容器添加 text-align: center;
- 元素有宽度的时候,可给块级元素添加 margin: 0 auto;使其水平居中。
- 给块级元素添加 position: absolute; left: 50%; 若元素宽度已知,则继续添加 margin-left: - (width / 2); 若宽度未知,则添加 transform: translateX(-50%);
- 使用flex布局,justify-content: center;
- 给容器添加 display: grid;块级元素添加 margin: 0 auto;
- 给容器添加 display: -webkit-box; -webkit-box-pack: center;
- 垂直居中:
- 内联元素可将其 height 和 line-height 设为相同值,使其垂直居中。
- 给容器添加 display: table-cell; vertical-align: middle;
- 给块级元素添加 position: absolute;top: 50%; 若元素高度已知,则继续添加 margin-top: - (height/ 2); 若高度未知,则添加 transform: translateY(-50%);
- 使用flex布局,align-items: center;
- 给容器添加 display: grid;块级元素添加 margin: auto 0;
- 给容器添加 display: -webkit-box; -webkit-box-align: center;
-
BFC
- BFC是指块级格式化上下文。它是一个独立的容器,容器里的元素不会影响到外面的元素,反之亦然。
- 形成BFC的条件:
- position为 absolute 或者 fixed。
- overflow不为 visible。
- float不为 none。
- display为inline-block、table-cell、table-caption、flex、inline-flex、gird、inline-grid
- BFC的特点:
- BFC区域不与浮动元素重叠。
- 属于同一个BFC的元素会发生margin重叠。
- BFC在计算高度的时候,会将里面的浮动元素也计算在内。
- BFC内部的box会在垂直方向依次排列。
- BFC的应用:
- 实现自适应的两栏布局(特点1)。
- 避免margin重叠(特点2)。
- 解决浮动元素的父元素高度塌陷问题(特点3)。
-
三栏布局(双飞翼、圣杯、flex)
- 要求:
- 三列布局,中间宽度自适应,两边宽度固定。
- 中间栏优先渲染。
- 实现:
- 双飞翼:
- 三栏全部浮动。
- 中间容器width占100%。
- 中间容器的内容添加margin。
- 左右栏使用margin-left拉出。
- 代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>双飞翼</title> <style> *{ margin: 0; padding: 0; } html,body{ height: 100%; } .container,.left,.right{ height: 100%; float: left; } .container{ width: 100%; } .content{ margin: 0 220px 0 200px; background: yellow; height: 100%; } .left{ width: 200px; background: red; margin-left: -100%; } .right{ width: 220px; background-color: green; margin-left: -220px; } </style> </head> <body> <div class="container"> <div class="content"></div> </div> <div class="left"></div> <div class="right"></div> </body> </html>
- 圣杯布局:
- 三栏全部浮动,且设置 position: relative;
- 容器设置 padding。
- 中间栏width占 100%。
- 左右栏用margin-left拉出,再用 left和 right摆正。
- 代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>圣杯</title> <style> *{ margin: 0; padding: 0; } html,body{ height: 100%; } .container{ height: 100%; padding: 0 220px 0 200px; } .content,.left,.right{ height: 100%; float: left; position: relative; } .content{ width: 100%; background-color: red; } .left{ width: 200px; background-color: green; margin-left: -100%; left: -200px; } .right{ width: 220px; background-color: yellow; margin-left: -220px; right: -220px; } </style> </head> <body> <div class="container"> <div class="content"></div> <div class="left"></div> <div class="right"></div> </div> </body> </html>
- flex:
- 容器设置 display: flex;
- 中间栏设置 flex: auto;
- 中间栏设置 order: -1;
- 代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>flex</title> <style> *{ margin: 0; padding: 0; } html,body{ height: 100%; } .container{ display: flex; height: 100%; } .content{ background-color: yellow; flex: auto; } .left{ width: 200px; background-color: red; order: -1; } .right{ width: 220px; background-color: green; } </style> </head> <body> <div class="container"> <div class="content"></div> <div class="left"></div> <div class="right"></div> </div> </body> </html>
- 双飞翼:
-
行内元素与块级元素区别
- 块级元素总是从新的一行开始,行内元素会和其他元素在一行。
- 块级可以容纳其他任何元素,行内元素只能容纳文本元素和其他行内元素。
- 块级元素width、height、padding、margin都可以改变。行内元素的width和height无效。margin只有左右有效,上下无效。padding上下左右都有效,会造成“撑大盒子”的效果,但不会影响布局。
-
position属性
- static:默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
- absolute:生成绝对定位的元素。相对于第一个 非static定位的父级元素进行定位。元素的位置通过 "left", "top", "right" 以及"bottom" 属性进行规定。
- fixed:生成固定定位的元素。相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
- relative:生成相对定位的元素,相对于其正常位置进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
- sticky:粘性定位,该定位基于用户滚动的位置。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像position:fixed;它会固定在目标位置。注意: Internet Explorer, Edge 15 及更早 IE 版本不支持 sticky 定位。 Safari 需要使用 -webkit- prefix。
-
浮动元素引起的父元素高度塌陷问题的解决方法
- 给父元素设置高度height。
- 使用 clear 清除浮动。
- 父元素形成BFC。
-
浏览器中设置更小字体
一般浏览器最小的字体尺寸为 12px,想要更小可以使用transform:scale(),但是元素的display不能为inline,其余都可以。
-
flex弹性布局
- 六个容器属性:
- flex-direction
- flex-wrap
- flex-flow
- justify-content
- align-items
- align-content
- 六个内容属性:
- order
- flex-grow
- flex-shrink
- flex-basis
- flex
- align-self
- 具体属性及其作用:Flex 布局教程:语法篇 - 阮一峰的网络日志
-
nth-child(n)和nth-of-type(n)的区别
- nth-child(n) 和 nth-last-child(n):第 n 个元素是否为指定元素,若是则使用样式。而不是第 n 个指定元素。
- nth-of-type(n) 和 nth-last-of-type(n):第 n 个指定元素使用样式。
- 参数 n:可以是数字、关键词或公式。
- 数字:代表第 n 个。
- 关键词:odd (奇数)、even(偶数)。
- 公式:(an + b)。其中 a 表示周期的长度,n 是计数器(从 0 开始),b 是偏移值。例如:nth-of-type(4n+2) 就是选择下标是4的倍数加上2的所有元素。
-
css选择器匹配规则
- css的匹配规则:从右向左。例如:div p a { .... },浏览器会先匹配a,再匹配p,最后匹配div。
- 优点:在每一个document文档中,html最终都会解析成一个DOM树,如果采用从左向右进行匹配,途遇到不匹配的时候就会采取回溯,这样就会导致性能变得极差。如果采用从右向左进行匹配,每一次匹配都会淘汰不匹配的选项,而且不需要回溯,性能十分友好。
-
viewport标签 和 meta标签
- viewport标签:viewport指的是移动端浏览器网页的可视窗口的大小(一般都说的是宽度)在前几年的网页页面制作主要是针对PC端,没有考虑过移动端的适配问题,手机显示区域比PC端要小的多,网页中的内容需要进行缩小才可以在手机端上完全显示,但是对于响应式布局就会有问题,所以添加了一个layout viewport(布局窗口,大部分为980px),这样在手机端上就会正常的显示PC端的响应式页面。
- meta标签:近年来,前端开发开始注重手机端的开发,但是由于之前引入的layout viewport的缘故,在对页面进行布局时常常以layout viewport大小进行布局,而不是对手机实际的viewpoint大小进行布局,所以要使用meta标签强制的将layout viewport大小和移动端实际的viewport大小设置为一样的,所以meta标签实际上解决的是一个历史遗留下的问题。
- 示例:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0""> 1. width:指宽度device-width(设备宽度,device中文意思就是“设备”) 2. initial-scale:指初始缩放比例 3. maximum-scale:指最大的缩放比例 4. minimum-scale:指最小的缩放比例 5. user-scalable:是否运行用户进行缩放
-
自适应布局和响应式布局
- 自适应布局:自己根据屏幕宽度的改变而改变。典型的写法不需要media判断,直接让每个元素通过相对的宽度,比如百分比、vh、em 、rem等来改变容器的大小,文字的大小。自适应是为了解决如何才能在不同大小的设备上呈现同样的网页。手机的屏幕比较小,宽度通常在600像素以下;PC的屏幕宽度,一般都在1000像素以上(目前主流宽度是1366×768),有的还达到了2000像素。同样的内容,要在大小迥异的屏幕上,都呈现出满意的效果,并不是一件容易的事。
- 自适应布局的问题:如果屏幕太小,即使网页能够根据屏幕大小进行适配,但是会感觉在小屏幕上查看,内容过于拥挤,响应式正是为了解决这个问题而衍生出来的概念。它可以自动识别屏幕宽度、并做出相应调整的网页设计,布局和展示的内容可能会有所变动。例如有六张图片,屏幕宽度大于1300像素,则6张图片并排在一行。如果屏幕宽度在600像素到1300像素之间,则6张图片分成两行。如果屏幕宽度在400像素到600像素之间,则导航栏移到网页头部。如果屏幕宽度在400像素以下,则6张图片分成三行。
- 响应式布局:根据设备的不同而展示不同的效果。典型的写法就是通过media判断,在不同的设备、分辨率下展示不同的页面效果。响应式的概念应该覆盖了自适应,而且涵盖的内容更多。
- 响应式布局的技术点:
- 允许网页的宽度自动的调整。
- 尽量少使用绝对的宽度,多点百分比。
- 相对大小的字体:字体不要使用px写死,最好使用相对大小的em,或者高清方案rem,这个不限制与字体,别的属性也可以这么设置。
- 流式布局,float等。float的好处是,如果宽度太小,放不下两个元素,后面的元素会自动滚动到前面元素的下方,不会在水平方向overflow(溢出),避免了水平滚动条的出现。
- 选择加载css。例如:<link rel="stylesheet" type="text/css" media="screen and (max-device-width: 400px)" href="tinyScreen.css" />,这个意思是如果屏幕宽度小于400像素(max-device-width: 400px),就加载tinyScreen.css文件。
-
CSS中可以和不可以继承的属性
详情见链接:CSS中可以和不可以继承的属性 - KerwinLee - 博客园
-
使用rem时根标签字体大小的设置
一般浏览器默认根标签的字体大小为 16px。使用rem时,为了方便换算单位,可将根标签大小设置为 10px,此时 1rem = 10px。但是一般为了适配会将根标签字体大小设置为 62.5%(10 / 16)。但是此处有有问题了,常用的Chrome浏览器的最小字体为 12px,也就是说根标签字体设置为 62.5%,不会到达10px。此时可将比例调大,比如使用 625%,此时 1rem = 100px。
//PC端自适应根标签字体
let designWidth = 1280;//设计稿宽度
changeFontSize();//初始化根标签字体大小
window.onresize = changeFontSize;
function changeFontSize(){
let clientWidth = document.documentElement.clientWidth >= designWidth ? designWidth : document.documentElement.clientWidth;
let fontSize = clientWidth * 10 / designWidth; // 1rem = 10px
document.getElementsByTagName('html')[0].style.fontSize = fontSize + 'px';
}
网络(TCP、HTTP)
-
get、post的区别、联系
- 两者本质上是TCP连接。
- get产生一个数据包,而post产生两个数据包(Firefox只产生一个),get将header、data一起发送,post先发送header,响应100后,再发送data。
- get参数通过url传送(不安全),post在请求体中传送。
- get请求时,浏览器会自动cache,post需要手动设置。
- get请求时,浏览器回退不会重复请求,post会再次请求。
- get中的参数长度有限,post没有。
- get的参数只支持ASCII,post没有限制。
-
HTTP的八种常用请求方法
get、post、put、delete、header、options、trace、connect
-
同源、跨域问题
- 页面的协议、域名、端口号一致,则为同源。
- 跨域的限制:
- 拒绝访问ajax返回的数据
- 拒绝访问DOM
- 拒绝cookie、webstorage、indexedDB
- 解决方案:
- JSONP:通过动态添加script标签,指定src,服务端把数据放入回调函数中,客户端通过回调函数获取数据。
- window.name:只要在同一个窗口下,就可以将数据放入window.name中,其他页面可以去访问。
- window.postmessage():此方法只能在window.open()返回的对象或者iframe的contentWindow属性上去调用。将要发送的数据传入该方法。在其他页面通过监听message事件,去获取数据。
- 片段标识符:在跳转页面时,在url后添加'#'加数据。
- CORS跨域资源共享(重点):一份浏览器技术的规范。浏览器会给客户端请求头添加origin字段,表明请求来自哪里。服务端需在响应头中添加以下字段:
- Access-Control-Allow-Origin: '*'
- Access-Control-Allow-Headers: 'Content-Type'
- Access-Control-Allow-Methods: 'GET、POST、PUT、DELETE、OPTIONS'
- Access-Control-Allow-Credentials: 'true'
- CORS两种请求:
- 简单请求:设置指定的请求方法和请求头。
- 非简单请求:除了简单请求,其他都是非简单请求。非简单请求在发送请求前,会先发送OPTIONS请求来验证这个非简单请求是否可以访问。
-
ajax
- ajax是一种用于创建快速动态网页的技术。它是无需加载整个页面就可以与服务器交换数据并更新部分网页内容的技术。
- 优点:减轻服务器负担,按需取数据,促进了页面和数据的分离。
- 缺点:需考虑浏览器的兼容。对流媒体和移动设备的支持不是很好。ajax只是局部刷新,所以页面后退按钮会失效。
- 为什么ajax要禁用浏览器缓存:ajax在处理重复数据的提交在同一url时,不会提交给服务器,而是从缓存中取得,这样会导致用户不能获取最新的数据。
- readyState: 0:请求未初始化。1:服务器已建立连接。2:请求已接收。3:请求正在处理。4:请求已完成且响应已就绪。
- status:
- 100~199:成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程。
- 200~299:表示成功接收请求且已完成了整个处理过程。
- 300~399:网页重定向。
- 400~499:客户端请求出错。
- 500~599:服务端出错。
- 常见状态码及含义:
- 100:客户端继续发送请求。
- 200(有三种情况):
- 强缓存。
- 内存缓存。
- 请求成功。
- 206:客户端发送了一个带有 Range 头的 get请求,服务端完成了它。
- 301:永久重定向。表示资源已经移动到了一个新的url上。
- 302:临时重定向。之前的url可能还有用。
- 304:客户端向服务端发起请求,服务端告诉客户端之前的缓冲文档还可以继续使用。(协商缓存)。
- 307:临时重定向。
- 400:客户端请求错误,如有语法错误。
- 401:请求未经授权。
- 403:服务端拒绝本次请求。
- 404:请求资源不存在。
- 405:请求方法不被允许。
- 408:请求超时。
- 500:服务端内部出错。
- 503:服务端暂时无法处理本次请求,一段时间后可能恢复正常。
- 504:请求代理服务器时,未能及时从远端服务器获取请求。
- 505:不支持请求协议。
- ajax强制断开连接的方法:使用 xhr.abort() 方法。
- 原生ajax:
function ajax(methods,url,data,success){
let xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP')
}
if(methods == 'get'){
url = url + '?' + data;
xhr.open('get',url,true);
xhr.send();
}else if(methods == 'post'){
xhr.open('post',url,true);
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");//post必须设置头字段
xhr.send(data);
}
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304){
success(xhr.responseText);
}else{
throw new Error('sorry' + xhr.status);
}
}
}
ajax('post','http://www.getage.com','age=1',function(data){console.log(data)});
-
axios的使用
- axios是请求后台资源的模块,它是基于promise的。它会将响应回来的内容自动转换为json类型。安全性高,客户端支持预防CSRF攻击。
- 常用的操作有axios.get()、axios.post()等方法。它们的第一个参数为要请求的服务器的url,后续的参数是请求的要求、数据等。它们都有then()方法和catch()方法,同promise的then()和catch()方法。
- axios默认不携带cookie,需手动设置。
-
浏览器缓存
- 强缓存:规定时间内,不询问服务器,强制使用浏览器缓存。时间的设置是根据响应头的expires字段和cache-control字段。expires的值是一个绝对时间,在此时间之前,浏览器都会使用强缓存。此方法有一个缺点:当客户端与服务端时间不同步时,可能出现不缓存的情况。cache-control字段是一个相对时间,在此时间段内,都会使用强缓存,它解决了expires的缺点。
- 协商缓存:浏览器会带着 If-None-Match字段和If-Modified-Since字段询问服务器是否使用缓存。服务器会将传来的 If-None-Match字段和 Etag字段进行比较,或将传来的 If-Modified-Since字段和 Last-Modify字段作比较,最后决定是否使用缓存。其中Etag可以保证每一个资源是唯一的,资源变化都会导致Etag变化。Last-Modify是一个时间,标识该资源的最后修改时间。
- 有了Last-Modified,为什么还要Etag:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET。
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)。
- 某些服务器不能精确的得到文件的最后修改时间。
- Last-Modified与ETag是可以一起使用的,服务器会优先验证Etag,当Etag一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
-
常用的请求头
- Cache-Control:指定请求和相应遵循的缓存机制。
- Connection:表示是否要持久连接。
- Referer:标识从哪个链接跳转。
- User-Agent:发出请求的用户信息。
- Pragma:用来包含实现特定的指令。
-
禁止浏览器缓存的方法
- 设置 Expires为 -1或0。
- 设置 Cache-Control为 no-cache。
- 设置 Pragma为 no-cache。
-
跳过缓存处理的方式
- F5:普通刷新。只会跳过强制缓存,但会检查协商缓存。
- Ctrl+F5:强制刷新。跳过强制缓存和协商缓存,直接从服务器加载。
-
互联网的五层模型(TCP)
- 实体层:连接电脑的物理手段,负责传输0、1电信号。
- 链接层:确定0、1的分组方式。
- 网络层:引进一套新地址(网址),用于区分不同计算机是否属于同一个子网络。
- 传输层:建立端口到端口的通信。
- 应用层:规定应用程序的数据格式。
- 注意:IP位于网络层。TCP位于传输层。HTTP位于应用层。
-
三握四挥
- 三次握手:
- 由客户端主动打开连接,发送一个SYN=1的请求报文,不能携带数据。客户端的状态变为“SYN-SENT(同步已发送)”
- 服务端接收到请求报文后,如果同意则发送一个SYN=1和ACK=1的响应报文,不能携带数据。服务器状态变为“SYN-RCVD(同步收到)”状态
- 客户端收到来自客户端的确认后,还需要再向服务端发送一个ACK=1的确认报文,可以携带数据。并且客户端的状态变为“ESTABLISHED(已连接)”,当服务端收到这个确认报文后状态也会变为“ESTABLISHED(已连接)”
- 为什么三次握手:
- 版本一:为了实现可靠数据传输。TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中,哪些是已经被对方收到的。 三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。
- 版本二:防止已失效的报文建立连接。如果已失效的请求建立连接报文突然又传到服务端,服务器就会认为客户端想要建立连接,这时就需要使用第三次握手来确认这个请求是否有效,如果没有第三次握手服务端便一直在等待客户端发送数据。一般失效的请求建立连接报文产生的过程为:
- 客户端发送(第一次)请求建立连接报文,但是客户端一直没收到确认,因为网络拥塞。
- 客户端重新发送(第二次)请求建立连接报文,这时服务端返回响应,并正常交互。
- 当第二次交互正常完成之后,这时第一次发送的请求建立连接报文突然来到服务端,这个第一次发送的建立连接报文就是已失效的报文。
- 四次挥手:
- 客户端发起FIN=1的释放连接请求报文,不再发送数据。这时客户端便会进入“FIN-WAIT-1”状态。
- 服务端收到释放连接的请求报文后,便会发送ACK=1的确认报文,同时会通知应用进程。如果这时服务端的数据还没有发送完成,服务端就会继续发送数据(这就是四次握手的原因,为什么不是三次)。这时服务端的状态变为“CLOSE-WAIT”,客户端收到这条响应报文后,还得继续接受数据,这时客户端的状态变为“FIN-WAIT-2”状态。
- 当服务端的数据发送完成之后,便会主动再次发送一个FIN=1和ACK=1的确认报文。这时服务端会进入“LAST-ACK”状态。
- 当客户端接受到第三次报文后,便会发送一个ACK=1的确认报文。这时客户端会进入“TIME-WAIT”状态,并且会等待2MSL的时间之后才会关闭。
- 为什么要等待2MSL的时间:
- 为了保证第四次发送的报文可以成功的抵达服务端。如果服务端在发送第三次报文后一直没得到响应,便会重传第三次报文,这时客户端需再次发送第四次报文。
- 第二为了保证本次连接时,发送的所有的报文已全部从网络中消失。
-
HTTP和TCP的关系
TCP在传输层,HTTP在应用层。HTTP是基于TCP连接上的应用。TCP来建立连接,HTTP来收发数据。
-
HTTPS
- HTTP三大风险:窃听、篡改、冒充。
- HTTPS使用 SSL/TLS 协议进行数据加密传输。
- SSL/TLS 的基本过程:
- 客户端向服务端索要并验证公钥。
- 双方协商生成“对话秘钥”。
- 双方采用“对话秘钥”进行加密通信。
-
一次完整的HTTP请求流程
- 域名解析。
- 发起TPC三次握手。
- 简历TCP连接后,发起HTTP请求。
- 服务端响应HTTP请求,浏览器得到HTML代码。
- 浏览器对HTML代码进行解析,并请求HTML中的资源。
- 浏览器对页面进行渲染并呈现给用户。
- 断开连接(TCP四次挥手)。
-
域名解析
- 域名解析是将域名解析为IP地址的过程。
- 过程:
- 查找浏览器缓存。
- 查找操作系统中的HOST文件中IP与域名的映射。
- 查找路由器缓存。
- 查找 ISP DNS缓存。
- 迭代查询:
- 本地DNS服务器请求互联网根域,根域将域名的顶级域的IP地址返回到本地DNS。
- 本地DNS根据返回的IP,向顶级域发送请求,顶级域将二级域的IP地址返回到本地DNS。
- 继续向下进行查询。直至本地DNS服务器得到了最终的查询结果,并将最后IP返回到主机。此时主机通过IP访问网站。
-
TCP和UDP的区别
- TCP是面向连接的(三次握手)。而UDP是无连接的。
- TCP只能是一对一的交互。而UDP可以进行一对一、一对多、多对一、多对多的交互。
- TCP可以保证数据的有序传输。UDP传输数据是无序的。
- TCP速度相对比较慢,它需要做更多的事情。UDP相对较快。
- TCP的数据报头大小是 20字节。UDP数据报头大小是 8字节。
- TCP有流量控制,UDP没有。
- TCP传输时不会丢包,UDP会丢包。
- TCP多用于金融领域。UDP多用于游戏、娱乐等。
- 基于TCP的协议:HTTP(HTTPS)、SMTP(邮件传输协议)、FTP(文件传输协议)、Telnet(远程登录协议)等。
- 基于UDP的协议:DNS(域名解析)、OICQ(聊天)等。
-
CSRF攻击和XSS攻击
- CSRF(跨站请求伪造):
- 必要条件:
- 登陆受信用网站A,并在本地生成cookie。
- 在不登出A的情况下,访问危险网站B。
- CSRF的防御:
- Token验证:服务器发送一个token给客户端,客户端提交表单中带着这个token,若token不合法,则服务器拒绝请求。
- 隐藏令牌。
- Referer验证:直接收来自本站的请求。
- 必要条件:
- XSS(跨域脚本攻击):
- 原理:不需要登录网站,它会通过合法的操作(在url中输入,在评论框中输入等),向页面注入脚本(js、html代码块等)进行攻击。
- 危害:
- 盗用网站cookie。
- 破坏页面结构,插入广告。
- D-doss攻击:恶意性的资源占用攻击,导致网页打开缓慢、服务器崩溃等。
- XSS攻击类型:
- 反射型:XSS代码出现在url中,作为输入提交到服务端,服务端响应后,XSS代码随响应内容一起传回页面,页面执行XSS代码。
- 存储型:第一次提交的XSS代码存储在服务端,下次请求的时候不用再次提交XSS代码。
- XSS的防御:
- 对用户输入的数据进行HTML Entity编码。
- 过滤:移除用户输入的和事件相关的内容,移除用户输入的style节点,script节点、iframe节点等。
- 校正输入。
- CSRF和XSS的区别:
- CSRF需要用户先登录网站,获取cookie。XSS不需要登录。
- CSRF是利用网站本身的bug,去请求它的api。XSS是向网站注入JS代码,然后执行JS代码,,篡改网站。
-
cookie、session、webstorage(sessionStorage和localStorage)、IndexDB的区别与联系
- cookie和session的区别:
- cookie存在客户端,session存在服务端。
- cookie中保存的是字符串,session中保存的是对象。
- cookie对客户端是可见的,不安全。session对客户端是透明的,更安全。
- cookie支持跨域名访问(需设置相同的document.domain),session不支持。
- cookie与webstorage的区别:
- cookie存储空间为4K左右,webstorage为5M左右。
- sessionStorage关闭浏览器窗口即失效。localStorage长久有效。cookie可设置失效时间。
- cookie始终在同源的http请求中携带,即便是不需要也会携带。而webStorage不会自动携带。
- webStorage的添加、删除等操作有现成的API,cookie没有。
- cookie、webStorage、IndexedDB的共同点:
- 都存储在客户端。
- 都遵循同源策略。
- IndexDB:
- IndexedDB 是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
- 键值对储存:IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 异步:IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 支持事务:IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 储存空间:IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
- 支持二进制储存:IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
- 同源限制:IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
-
Token的使用
- token的作用:
- 防止表单重复提交:用户提交表单后,会携带token到服务器,服务器将session中的token和用户请求带过来的token进行比较,如果相同,会将session中的token进行更新。若用户重复提交,则用户之后发过来的请求的token和服务器session中的token是不一致的,所以会导致之后的表单提交操作失败。
- 用来作身份验证(防止CSRF攻击):如果用户是跨站点伪造的请求,在验证token的时候会发现客户端请求的token和服务器session中的token是不一致的,所以会导致请求的失败。
- token的传递:
- 放在请求头中:
//示例: $.ajax({ type: "POST", headers: { Accept: "application/json; charset=utf-8", userToken: "" + userToken //转换为字符串 } url: "/index", data: JSON.stringify(mytable.params), contentType: "application/json", dataType: "json", success:function(data){}, error:function(data){} });
- 使用beforeSend方法设置请求头:
//示例: $.ajax({ type: "POST", url: "/index", data: JSON.stringify(mytable.params), contentType: "application/json", dataType: "json", beforeSend: function(request) { request.setRequestHeader("Authorization", token); }, success: function(data) {}, error: function(data) {} });
- 放在请求头中:
- token的存储:最好存储在localstorage中。(注意设置过期时间)
NodeJS
-
querystring.stringify() / parse() 和 JSON.stringify() / parse() 的区别
- querystring:需用 require('querystring') 引入。
- querystring.stringify():把 URL参数对象解析为url参数字符串格式(用 & 分开,用 = 连接键值对 的字符串)。
- querystring.parse():把url参数字符串格式(用 & 分开,用 = 连接键值对 的字符串)解析为URL参数对象。
- JSON:
- JSON.stringify():把对象解析为JSON字符串。
- JSON.parse():把JSON字符串(有对象格式的)解析为对象。
- 代码示例:
const querystring = require('querystring') let o = { name: 'wang', age: 21 } let jsonO = JSON.stringify(o); let queryO = querystring.stringify(o); console.log(typeof jsonO + '类型: ',jsonO) // string类型: {"name":"wang","age":21} console.log(typeof queryO + '类型: ',queryO)// string类型: name=wang&age=21 console.log(typeof JSON.parse(jsonO) + '类型: ',JSON.parse(jsonO)) // object类型: { name: 'wang', age: 21 } console.log(typeof querystring.parse(queryO) + '类型: ',querystring.parse(queryO)) // object类型: { name: 'wang', age: '21' }