1.正则 www.jianshu.com/p/81653d3a4…
判断参数的类型:
function isType(params){
let typeREG = /(?<obj>object) (?<type>[a-zA-Z]+)/
return typeREG.exec(Object.prototype.toString.call(params)).groups.type
}
let reDate=/(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/, d = '2019-10-31';
let match = reDate.exec(d)
2.video初研究
在手机(Android还是iOS)中video不能够自动播放,而且在微信中video标签全屏时会出现讨厌的x5内核给我们的东西并不是我们想要的纯净的h5video标签此时需要添加属性
<video src="..." poster="..." controls x5-video-player-fullscreen="true"
webkit-playsinline x5-video-player-type="h5-page"></video>
3.正则
字符串中是否有特定的值: ..includes(**)截取字符串特定的值: ...indexOf(**) ss.substring(0,aa)正则表达式:reg= new RegExp(/(\{\{)(.*)(\}\})/,'g') ....match(reg) ==> ['sss','sfd']
4.拷贝
浅拷贝,深拷贝
拷贝的方式:
- Object.assign
- JSON.parse JSON.stringify
- ladash.cloneDeep()
- 递归方式实现
5.viewport
var devicePixelRatio = window.devicePixelRatio
var initialScale = 1 / devicePixelRatio
var maximumScale = 1 / devicePixelRatio
var minimumScale = 1 / devicePixelRatio
var meta = document.createElement('meta')
meta.name = 'viewport'meta.content = 'width=device-width,initial-scale=' + initialScale + ',maximum-scale=' + maximumScale + ',minimum-scale=' + minimumScale + ',user-scalable=no'
document.head.appendChild(meta)
6. call,apply 函数实现
function jawiiSymbol(obj){
var unique_proper = "00" + Math.random();
if(obj.hasOwnProperty(unique_proper)){
arguments.callee(obj);
}else{
return unique_proper;
}
}
Function.prototype.myApply = function(context){
var context = context || window;
var fn = jawiiSymbol(obj);
context[fn] = this;
const value = eval('context['+ fn +']('+(arguments[1]||[]).toString()+')');
delete context[fn];
return value;}
Function.prototype.myCall = function (){
return this.myApply(Array.prototype.shift.myApply(arguments),Array.prototype.slice.myApply(arguments,1))
}
Function.prototype.bind = Function.prototype.bind || function(context){
var me = this;
var args = Array.prototype.slice.myCall(arguments,1);
var F = function (){};
F.protoype = this.prototype;
var bound = function (){
var innerArgs = Array.prototype.slice.myCall(arguments,1);
var finalArgs = args.concat(innerArgs);
return me.myApply(this instanceof F ? this : context || this,finnalArgs);
}
bound.prototype = new F();
return bound;
}
7. 类的继承
-
原型链的继承
function Animal(name){ this.name = name || 'Animal' this.sleep = function(){ console.log(this.name + '正在睡觉') } } Animal.prototype.eat = function(food){ console.log(this.name + '正在吃' + food) } function Cat(){} Cat.prototype = new Animal() Cat.prototype.name = 'Cat' let cat = new Cat() // 特点: 基于原型链,既是父类的实例,也是子类的实例 // 缺点: 无法实现多继承 -
构造继承
function Cat(name){ Animal.call(this) this.name = name || 'Tom' } // 特点: 可以实现多继承 // 缺点: 只能继承父类实例的属性和方法,不能继承原型上的属性和方法 -
组合继承
function Cat(name){ Animal.call(this) this.name = name || 'Tom' } Cat.prototype = new Animal() Cat.prototype.constructor = Cat // 特点: 可以实现继承实例属性/方法,也可以继承原型属性/方法 // 缺点: 调用了两次父类构造函数,生成了两份实例集合 -
寄生组合继承
function Cat(name){ Animal.call(this) this.name = name || 'Tom' } (function(){ var Super = function (){} Super.prototype = Animal.prototype Cat.prototype = new Super() })()
8.防抖和节流
-
防抖:在事件触发n秒后在执行回调,如果在这n秒内又被触发,则重新计时
-
如果短时间内大量触发同一事件,只会执行一次函数。
-
在固定时间内如果任务没有执行完,又有任务触发,则重新定时
let debounce = (fn,delay)=>{ let timer = null return (...args)=>{ clearTimeout(timer) timer = setTimeout(()=>{ fn.apply(this,args) },delay) } } // 适用场景: // 按钮调教场景,防止多次提交按钮,只执行最后提交的一次 // 搜索场景 //带有 function debounce(fn,wait,immediate){ let timer = null; return function (){ let args = arguments; if(timer)(clearTimeout(timer)); if(immediate){ let callNow = !timer; timer = setTimeout(()=>{ timer = null; },wait) if(callNow){ fn.apply(null,args) } }else{ timer = setTimeout(()=>{ fn.apply(null,args) },wait) } }}function throttle(fn,wait,immediate){ let timer = null; return function (){ let args = arguments; if(timer){return}; timer = setTimeout(()=>{ timer = null; if(!immediate){ fn.apply(null,args) } },wait) if(immediate){ fn.apply(null,args); } }} -
节流:规定在一个单位时间内,只能触发一次函数,如果在这个时间内多次触发,只有一次生效
-
如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
-
固定时间内只有第一次执行,执行完后才可以执行下一个任务
const throttle = (fn,delay=500)=>{ let timer = null; return (...args)=>{ if(timer) return timer = setTimeout(()=>{ fn.apply(this,args) },delay) }} // 使用场景: // 拖拽场景: 固定时间内只执行一次,防止超高频触发位置变动 // 缩放场景: 监控浏览器resize // 动画场景: 避免短时间内多次触发动画导致性能问题
9.订阅和发布
let publisher = {
handlers:{},
subscribe:function(name,handler){
this.handlers[name] = handler
},
publish:function(name,...args){
let handler = this.handlers[name]
if(handler){
handler(...args)
}
}
}
publisher.subscribe('say',function(something){console.log(something)})
publisher.publish('say','你好')
10.import 和require的区别
- 规范:
require是AMD规范引入方式
import是es6的一个语法标准,如果要兼容浏览器的话需要转化成es5的语法
- 调用时间
require是运行时调用,所以require理论上是可以运用在代码的任何地方
import是编译时调用,所以必须放在文件的开头
- 本质
require是赋值的过程,其实require的结果就是对象,数字,字符串,函数等,再把require的结果赋值给某个变量(值的拷贝)
import是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持es6,也是将es6转码为es5在执行,import语法会被转码为require(值的引用)
ES6 模块与 CommonJS 模块的差异
它们有两个重大差异:
① CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
② CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
11.将类数组对象转换成数组
1.Array.prototype.slice.call()
function foo(a,b){
let res = Array.prototype.slice.call(arguments)
console.log(res)
}
let json = {
0:'aaa',
1:'bbb',
2:'ccc',
length:3
}
let resJ = Array.prototype.slice.call(json)
2.Array.from()
function foo(a,b){
let res = Array.from(arguments)
console.log(res)
}
let json = {
0:'aaa',
1:'bbb',
2:'ccc',
length:3
}
let res = Array.from(json)
3.扩展运算符
function foo(a,b){
let res = [...arguments]
}
let json = {
0:'aaa',
1:'bbb',
2:'ccc',
length:3
}
此时,扩展运算符对json格式的类数组对象失败,因为该对象没有iterable没有定义遍历器(Iterator)接口
let jsonIterable = {
0:'aaa',
1:'bbb',
2:'ccc',
length:3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
就可以了
12.原型和原型链
构造函数,函数,对象
**prototype:**在js中每个函数都有一个prototype属性,它的值对应着该函数的原型对象
**__proto__:**在js中每个对象都有一个__proto__属性,它的值对应着该对象的原型对象
**原型的概念:**每一个js对象(除null外)创建的时候,就会关联另一个对象,这个对象就是我们所说的原型对象,每一个对象都会从原型中继承属性
**constructor:**每个原型对象都有一个constructor属性,指向该关联的构造函数
**原型链:**每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例对象都包含一个指向原型对象的指针。让原型对象等于另一个类型的实例对象,此时,原型对象将包含一个指向另一个原型对象的指针,如此层层递进,就构成了实例与原型对象的链条。
Object.prototype的原型对象是谁呢?
console.log(Object.prototype.__proto__ === null ) // true
null表示没有对象,即该处不因该有值
Object.prototype.__proto__ 的值为null 和Object.prototype没有原型对象是一个意思
13.typeof
typeof 运算符可以判断表达式的类型,返回7中结果:number,string,boolean,object,function,symbol,undefined
typeof的用法有两种:
typeof a ; || typeof(a)
14.instanceof
instanceof运算符用来判断对象是否是指定对象的实例
a(实例对象) instanceof Object(构造函数)
15. for of for in
1.for in 循环 遍历数组的问题
- index索引为字符串型数字,不能直接进行几何运算
- 遍历顺序有可能不是按照实际数组的内部顺序
- 使用for in 会遍历数组所有的可枚举属性,包括原型。例如上面的原型方法和属性
- 使用for in 更适合遍历对象
for in 遍历的数组的索引而for of 遍历的是数组值
for of 遍历的只是数组内的元素,不包括数组的原型方法和属性
2.遍历对象
遍历对象通常使用for in 来遍历对象的键名
for in 可以遍历到对象的原型方法和属性,如果不想遍历原型的内容,可以在循环内部判断一下,hasOwnProperty方法可以判断某属性是否是该对象的实例属性
for (var key in myObject) {
if(myObject.hasOwnProperty(key)){
console.log(key);
}
}
也可以使用Object.keys()获取对象的键名组成的数组,不包括原型方法和属性
总结:
for ... of 使用遍历数/数组/数组对象/字符串/map/set等拥有迭代器对象的集合,但是不能遍历对象,因为没有迭代对象,与forEach不同的是,它可以正确响应break、Continute和return语句。
for ... in 可以用来遍历对象
16.闭包
环境栈
每个函数或块都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入“环境栈”中。函数执行完后,栈将其弹出并销毁变量对象,然后把控制权返回在给之前的执行环境。如果内执行环境的变量对象,被外部执行环境引用,那么内部环境变量对象就无法被销毁(如:闭包)。
17.Object.is()和===的区别
Object.is在严格等于的基础上修复了一些特殊情况下的失误,具体来说就是具体来说就是+0和-0,NaN和NaN。 源码如下:
function is(x, y) {
if (x === y) {
//运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的
return x !== 0 || y !== 0 || 1 / x === 1 / y;
} else {
//NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理
//两个都是NaN的时候返回true
return x !== x && y !== y;
}
18.BFC是什么?应用的场景,有什么作用,如何形成BFC?
BFC是什么?
- 块级格式化上下文(Block Formatting Contexts)
有什么作用?
- 简单来说BFC就是一个独立不受外界干扰也不干扰外界的盒子. 是web页面中盒模型布局的css渲染模式,一个隔离的独立容器,
形成BFC的条件
- 浮动:float元素除了none以外的值。
- 定位:绝对定位,相对定位。
- display为以下的值之一:inline-block,table-cell,table-captionoverflow除了visible以外的值(hidden,auto,scroll)
BFC的特性:
- 内部的box会在垂直方向上一个接一个的放置;
- 垂直方向上的距离由margin决定。
- BFC区域不会与float元素区域重叠。
- 计算BFC的高度时,浮动元素也参与计算。
- BFC就是页面上的一个独立容器,里面的子元素不会影响外面的元素。外面的元素也不会影响里面的
19.数组
1. 随机乱序
[1,2,3,4,5].sort((a,b)=>Math.random()>0.5?1:-1)
2. value为key的数组
Array.from({length:7},(_,i)=>i)
Array.form({length:7}).map((_,i)=>i)
new Array(7).map((_,i)=>i)
Array.of(0,1,2,3,4,5,6)
20. 手写Events事件
class Events{
constructor(){
this._events={}
}
on(name,fn,...argOrg){
// 必传参数验证
if (!name || !fn) {
throw new Error(`[Events TypeError] Failed to execute 'Events' on '${name}' : arguments required`)
return
}
// 阻止重复添加相同的监听
let fns = this._events[name] || []
if(fns.find(item=> item.fnOrg===fn)){return;}
this._events[name] = fns.concat({fn: arg => fn.apply(null, [...argOrg, ...arg]),fnOrg:fn})
}
once(name, fn, ...argOrg) {
const onFn = (...arg) =>{
fn.apply(null, arg)
this.off(name, onFn)
}
this.on(name, onFn, ...argOrg)
}
emit(name, ...arg) {
(this._events[name]||[]).forEach(item =>{item.fn(arg)})
}
off(name,fn) {
// 无参数 : 清掉所有监听
if(!arguments.length){
this._events = Object.create(null)
}
// 一个参数 : 清掉该事件名下所有监听
if(arguments.length== 1){
delete this._events[name]
}
let fns = this._events[name];
if(!fns || !fns.length)return;
this._events[name] = (fns||[]).filter(item=>{return item.fnOrg !== fn});}
}
const event = new Events();
const fn1 = (...args)=>console.log('I want sleep1',...args);
const fn2 = (...args)=>console.log('I want sleep2',...args);
// 订阅
event.on('sleep', fn1, 1, 2, 3);
event.on('sleep', fn2, 1, 2, 3);
// 发布
event.emit('sleep', 7,8,9);
event.off("sleep",fn1);
event.once("sleep",()=>console.log("I want sleep"));
event.emit("sleep");event.emit("sleep")
I want sleep1 1 2 3 7 8 9
index.html:58 I want sleep2 1 2 3 7 8 9
index.html:58 I want sleep2 1 2 3
index.html:64 I want sleep
index.html:58 I want sleep2 1 2 3
class Event { _event = new Map(); on(eventName,fn){ if( this._event.has(eventName)){ const fnSet = this._event.get(eventName); if(!fnSet.has(fn)){ fnSet.add(fn); } }else{ const fnset = new Set([fn]); this._event.set(eventName,fnset); } } emit(eventName,...args){ if(this._event.has(eventName)){ this._event.get(eventName).forEach(fn=>fn.apply(null,[...args])); } } off(name,fn){ if(arguments.length ===0){ this._event.clear(); } if(arguments.length ===1){ this._event.delete(name) } const fnSet = this._event.get(name); if( typeof fnSet === 'object' ){ fnSet.delete(fn); } } once(name,fn){ const fns = (...args)=>{ fn.apply(null,args); this.off(name,fns) } this.on(name,fns); }}
21.用闭包实现单例模式
单例:保证一个类仅有一个实例,并提供一个它的全局访问点
主要解决:一个全局使用的类频繁得创建与销毁
何时使用:当想要控制实例数目,节省系统资源的时候
如何解决:判断系统是否有这个实例,有则返回,无则创建
let Singleton = (function(){
let Head = function(){}
let instance = null;
return function(){
if(instance){
return instance
}
instance = new Head();
return instance
}
})()
let a = new Singleton()
let b = new Singleton()console.log(Object.is(a,b))
22.对象扁平化
function flatten(value){
const checkType = (value)=>/(?<obj>object) (?<type>[a-zA-Z]+)/.exec(Object.prototype.toString.call(value)).groups.type;
const returnObj = {};
(function getKeysFn(o,char){
for(let key in o){
let newChar = char ===""? key:`${char}.${key}`;
if(checkType(o[key])==="Object"){
getKeysFn(o[key],newChar);
}else{
returnObj[newChar ] = o[key]
}
}
})(value,"")
return returnObj;
}
let obj = {}
flatten({a:1,b:{c:2,d:{e:3}},f:{g:4}}) // {a:1,'b.c':2,'b.d.e':3,'f.g':4}
23. curry函数柯里化
function createCurry(handler,...args){
let allArguments = args;
let fn = function(){
allArguments.push(...arguments);
return fn;
}
fn.toString = function(){
return allArguments.reduce(handler)
}
return fn;
}
function curry(fn,...args){ const len = fn.length; return function(...params){ let _args = [...args,...params]; if(_args.length < len){ return curryB.call(this,fn,..._args); } return fn.apply(this,_args) }}
22. ==和===的区别
== 运算符称作相等,用来检测两个操作数是否相等,可以允许进行类型转换
===运算符称作全等,用来检测两个操作数是否严格相等
- 对于string,number等基础类型,==和===是有区别的,不同类型间比较,==比较转化成同一类型后的值看值是否相等,===如果类型不同,其结果就不等,同类型比较,直接进行值比较,两者结果一样。
- 对于Array,Object等高级类型,==和===是没有区别的
- 基础类型和高级类型,==和===是由区别的,对于==,将高级类型转为基础类型,进行值比较,因为类型不同,===结果为false
23. 0.1+0.2为什么不等于0.3,如何解决
在JavaScript中0.1+0.2!==0.3,这是因为JavaScript中的二进制浮点数0.1和0.2并不是十分精确,他们相加的结果并非等于0.3,而是一个比较接近的数字,0.30000000000000004,所以判断结果为false;
最好的办法是设置一个误差范围值,通常称为“机器精度”,而对于JavaScript来说,这个值通常是2^-52,而在ES6中Number.EPSILON,这个值正好等于2^-52,这个时候,我们只需要判断0.1+0.2-0.3小于Number.EPSILON,在这个误差范围内,就可以判定为true
function numberSequal( a , b ){
return Number.EPSITON ? Math.abs(a-b) < Number.EPSILON : Math.abs(a-b) < Math.pow(2,-52)
}
24.数组扁平化
function arrayFlat(arr){
while(arr.some(item=>Array.isArray(item))){
arr = [].concat(...arr)
}
return arr;
}
25.箭头函数
1. this指向:
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
2.通过call,apply,bind调用
由于箭头函数没有自己的this,通过call方法调用一个箭头函数时,只能传递参数,他们的第一个参数会被忽略
3.不绑定arguments
箭头函数不绑定arguments对象,
4.不能用new操作符调用
箭头函数不能用作构造器,和new一起用会抛出错误
5.没有prototype属性
6.箭头函数中通常不能用yield关键字,(除非是嵌套在允许使用的函数内),因此箭头函数不饿能用作生成器
为什么箭头函数不能使用new操作符
new操作符做了4件事情,
1.新建一个空对象 let obj = new Object()
2.将新对象的obj.__proto__ = Fun.prototype
3.将新对象作为函数的this, Fun.call(obj)
4.返回这个新对象 如果构造函数有return 判断return的数据类型,如果是引用类型,则返回return的对象,否则返回新创建的对象。
26.图片加载
document.addEventListener('DOMContentLoaded',function(){
var lazyImages = document.getElementsByClassName('lazy-images');
var observe = new IntersectionObserver(function(entries,observer){
entries.forEach(function(entry){
if(entry.isIntersecting){
var lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
observer.unobserve(lazyImage);
}
})
});
lazyImages.forEach(function(lazyImage){
observe.observe(lazyImage)
})
});