Q1
"123456789".match(/\d{3,5}?/g) // => ["123", "456", "789"]
Q2
var temp = 123;
function f(){
console.log(temp); // => undefined
if(false){
var temp = 456;
}
}
f();
Q3
var temp = 123;
function f(){
console.log(temp); // => 123
if(true){
console.log(temp); // Uncaught ReferenceError: temp is not defined
let temp = 456;
}
}
f();
Q4
new Promise(resolve => {
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
resolve();
}).then(() => console.log(4));
console.log(5)
// 上面输出结果: `1 5 3 4 2`
Q5
var name = 0;
var a = {
name: 1,
f1:function(){
this.name = 2;
},
f2:function(){
this.name = 3;
return null;
},
f3: function(){
this.name=4;
return {};
},
f4:function(){
var name = 5;
console.log(this.name);
},
f5:() => {
var name = 6;
console.log(this.name);
},
};
var o1 = new a.f1;
console.log(o1.name); // 2
var o2 = new a.f2();
console.log(o2.name); // o2=>{ name: 3 } 3
var o3= new a.f3;
console.log(o3.name); // o3=>{} undefined
a.f4(); // 1
f4 = a.f4;
f4(); // 0
f4.call(a); // 1
a.f5(); // 0
每个函数的 this 是在调用 时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。箭头函数指向词法作用域
Q6 模拟bind
Function.prototype.mybind = function(context) {
var self = this;
var args = [];//保存bind函数调用时传递的参数
for(var i = 1, len = arguments.length; i< len;i ++) {
args.push(arguments[i]);
}
//bind()方法返回值是一个函数
return function() {
//哇,新创建的函数传进来的参数可以在这里拿到哎!!
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs))
}
}
改进
Function.prototype.mybind = function (context) {
if (typeof this !== "function") {
throw new Error(this + "is not a function");
}
var self = this;
var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
var fbound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof self ? this : context, args.concat(bindArgs));
}
fbound.prototype = Object.create(self.prototype);
//返回的函数不仅要和 被调函数的函数体相同,也要继承人家的原型链
return fbound;
}
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
“快速排序”的思想很简单,整个排序过程只需要三步:
(1)在数据集之中,选择一个元素作为”基准”(pivot)。
(2)所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。
(3)对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
sessionStorage 、localStorage 和 cookie 之间的区别(常见)
共同点:用于浏览器端存储的缓存数据
不同点:
(1)、存储内容是否发送到服务器端:当设置了Cookie后,数据会发送到服务器端,造成一定的宽带浪费;
web storage,会将数据保存到本地,不会造成宽带浪费;
(2)、数据存储大小不同:Cookie数据不能超过4K,适用于会话标识;web storage数据存储可以达到5M;
(3)、数据存储的有效期限不同:cookie只在设置了Cookid过期时间之前一直有效,即使关闭窗口或者浏览器;
sessionStorage,仅在关闭浏览器之前有效;localStorage,数据存储永久有效;
(4)、作用域不同:cookie和localStorage是在同源同窗口中都是共享的;sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
浏览器的内核分别是什么?
IE: trident /traidnt/内核
Firefox:gecko /geko/ 内核
Safari:webkit内核
Opera:以前是presto内核,Opera现已改用Google Chrome的Blink内核
Chrome:Blink /blink/ (基于webkit,Google与Opera Software共同开发)
px和em的区别(常见)
相同点:px和em都是长度单位;
异同点:px的值是固定的,指定是多少就是多少,计算比较容易。em得值不是固定的,并且em会继承父级元素的字体大小。
浏览器的默认字体高都是16px。所以未经调整的浏览器都符合: 1em=16px。那么12px=0.75em, 10px=0.625em。
判断一个字符串中出现次数最多的字符,统计这个次数
var str = 'asdfssaaasasasasaa';
var json = {};
for (var i = 0; i < str.length; i++) {
if (!json[str.charAt(i)]) {
json[str.charAt(i)] = 1;
} else {
json[str.charAt(i)]++;
}
};
var iMax = 0;
var iIndex = '';
for (var i in json) {
if (json[i] > iMax) {
iMax = json[i];
iIndex = i;
}
}
console.log('出现次数最多的是:' + iIndex + '出现' + iMax + '次');
请写出你对闭包的理解,并列出简单的理解
使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
闭包有三个特性:
1.函数嵌套函数
2.函数内部可以引用外部的参数和变量
3.参数和变量不会被垃圾回收机制回收
new操作符到底到了什么
(1)创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
(2)属性和方法被加入到 this 引用的对象中。
(3)新创建的对象由 this 所引用,并且最后隐式的返回 this 。
null和undefined的区别?
null是一个表示”无”的对象,转为数值时为0;undefined是一个表示”无”的原始值,转为数值时为NaN。
undefined:
(1)变量被声明了,但没有赋值时,就等于undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
null:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
DOM怎样添加、移除、移动、复制、创建和查找节点
// 创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
// 添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
// 查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值(IE容错能力较强,会得到一个数组,其中包括id等于name值的)
getElementById() //通过元素Id,唯一性
从输入url到显示页面,都经历了什么
一般会经历以下几个过程:
1、首先,在浏览器地址栏中输入url
2、浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。
3、在发送http请求前,需要域名解析(DNS解析)(DNS(域名系统,Domain Name System)是互联网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住IP地址。),解析获取相应的IP地址。
4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。(TCP即传输控制协议。TCP连接是互联网连接协议集的一种。)
5、握手成功后,浏览器向服务器发送http请求,请求数据包。
6、服务器处理收到的请求,将数据返回至浏览器
7、浏览器收到HTTP响应
8、读取页面内容,浏览器渲染,解析html源码
9、生成Dom树、解析css样式、js交互
10、客户端和服务器交互
事件委托
事件委托就是利用的DOM事件的事件捕获阶段。把具体dom上发生的事件,委托给更大范围的dom去处理。好比送信员,如果每次都把信件送给每一户,非常繁琐。但是如果交给一个大范围的管理者,比如小区的传达室,那么事情会变得非常简单。事件委托就类似这种原理,我页面中有很多按钮,如果不使用事件委托,我只能在每个按钮上注册事件。非常麻烦。但如果我把事件注册在一个大范围的div(假设所有的按钮都在这个div中),那么我只要注册一次事件,就可以处理所有按钮(只要按钮包含在上述div中)事件的响应了
浏览器是如何渲染页面的?
渲染的流程如下:
1.解析HTML文件,创建DOM树。
自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。
2.解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式<HTML中的style样式;
3.将CSS与DOM合并,构建渲染树(Render Tree)
4.布局和绘制,重绘(repaint)和重排(reflow)
谈谈垃圾回收机制方式及内存管理
回收机制方式
1、定义和用法:垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。
2、原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
3、实例如下:
function fn1() {
var obj = {
name: 'hanzichi',
age: 10
};
}
function fn2() {
var obj = {
name: 'hanzichi',
age: 10
};
return obj;
}
var a = fn1();
var b = fn2();
fn1中定义的obj为局部变量,而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指向,所以该块内存并不会被释放。
4、垃圾回收策略:标记清除(较为常用)和引用计数。
请解释JSONP的工作原理,以及它为什么不是真正的AJAX
JSONP (JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的javascript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA需要的数据,然后在 pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。
AJAX是不跨域的,而JSONP是一个是跨域的,还有就是二者接收参数形式不一样!
浏览器缓存有哪些,通常缓存有哪几种
一、http缓存
二、websql
cookie
localstorage
sessionstorage
flash缓存
JS哪些操作会造成内存泄露
1)意外的全局变量引起的内存泄露
function leak(){
leak=”xxx”;//leak成为一个全局变量,不会被回收
}
2)闭包引起的内存泄露
3)3)没有清理的DOM元素引用
4)被遗忘的定时器或者回调5)子元素存在引起的内存泄露
JS中不太著名的一些坑
// 坑1
0.1 + 0.2 === 0.3
0.01+0.02 === 0.03;
console.log(0.1e-1+0.2e-1)
// 坑2
var date = new Date();
date.setFullYear(2019);
date.setMonth(1)
date.setDate(1)
console.log(date.getMonth()) // => ?
// 坑3
var arr = Array(3).fill({name: 'Jack'});
arr[0].name = 'Tom';
console.log(arr) // [{name:'Jack'},{name:'Jack'},{name:'Jack'}]
typeof null === ‘object’ 原理
原理是这样的,不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判 断为 object 类型,null 的二进制表示是全0, 自然前三位也是0,所以执行typeof 时会返回 “object”
Redux middleware 是什么
如果没有中间件的运用,redux 的工作流程是这样 action -> reducer,这是相当于同步操作,由dispatch 触发action后,直接去reducer执行相应的动作。但是在某些比较复杂的业务逻辑中,这种同步的实现方式并不能很好的解决我们的问题。比如我们有一个这样的需求,点击按钮 -> 获取服务器数据 -> 渲染视图,因为获取服务器数据是需要异步实现,所以这时候我就需要引入中间件改变redux同步执行的流程,形成异步流程来实现我们所要的逻辑,有了中间件,redux 的工作流程就变成这样 action -> middlewares -> reducer,点击按钮就相当于dispatch 触发action,接下去获取服务器数据 middlewares 的执行,当 middlewares 成功获取到服务器就去触发reducer对应的动作,更新需要渲染视图的数据。中间件的机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。
[参考文章](https://www.cnblogs.com/canfoo/p/6119446.html
🌱
applyMiddleware 源码分析
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
compose 源码
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
const fn = (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
return fn
}
var fn1 = (f) => {
console.log('fn1')
return f;
};
var fn2 = (f) => {
console.log('fn2')
return f;
};
[fn1, fn2].reduceRight((pre, cur) => cur(pre), f => f);
// 输出结果:
// fn2
// fn1
// f => f
Vue 实现原理分析
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
-
实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
-
实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
-
实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
下面的代码是否会造成堆栈溢出
function foo(){
setTimeout(foo, 0);
}
foo();
常见的webAPIs有哪些?
setTimeout、setInterval、 XHR 、fetch 、Promise 、DOM
####
JS事件循环机制(event loop)之宏任务/微任务
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 输出结果
// script start, script end, promise1, promise2, setTimeout
解读:
- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数
- 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
- 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
- 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
解读上面代码的执行顺序:
-
ajax进入Event Table,注册回调函数success
-
执行console.log(‘代码执行结束’)
-
ajax事件完成,回调函数success进入Event Queue
-
主线程从Event Queue读取回调函数success并执行
####
微任务(Microtasks)、宏任务(task)?
微任务和宏任务都属于异步任务,他们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值。
js异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入Event Loop,然后在执行微任务,将微任务放入eventqueue最骚的是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的queue上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。
- 而宏任务一般是:包括整体代码script,setTimeout,setInterval、setImmediate。
- 微任务:原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver 记住就行了。
setTimeout(()=>{
console.log('setTimeout1')
},0)
let p = new Promise((resolve,reject)=>{
console.log('Promise1')
resolve()
})
p.then(()=>{
console.log('Promise2')
})
// 输出结果
// Promise1, Promise2, setTimeout1
fetch API
fetch 缺点:
-
fetch只对网络请求报错,对400,500都当成成功的请求,需要封装处理
-
fetch 默认不会带cookie,需要在头部添加 credential: ‘include’
-
fetch 不支持abort, 不支持超时控制,使用setTimeout以及Promise.reject的实现的超时控制并不能阻止请求继续在后台运行,造成流量浪费
-
fetch没有办法检测请求的进度,而 XHR可以
fetch 优势:
- 更加底层,提供的API丰富(request, response)
- 脱离了XHR,是ES规范里新的实现方式
- 可以跨域设置 mode: ‘cros’
Promise
传入promise中的方法会被立即执行
then方法会缓存传入的函数,但不会立即执行
resolve中循环执行then方法中的函数
promise简单实现原理
function MyPromise(fn){
var self = this;
var status = 'PEDDING';
var deferred = []; // 主要用于存放 then方法中的回调函数
self.value = null;
this.then = function(onfulfilled) {
if(status === 'PEDDING') {
deferred.push(onfulfilled)
}
};
function resolve(value){
if(value !== null && value.then && typeof value.then === 'function'){
return value.then(resolve);
}
setTimeout(function(){
if(status === 'PEDDING') {
status = 'FULFILLED';
self.value = value; // 成功后会得到一个值,这个值不能改
deferred.forEach(function(cb){
cb(value);
})
}
})
}
fn(resolve);
}
防抖函数与节流函数
- 函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
- 函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。 防抖的应用场景:(输入框请求api, 窗口的resize) 节流的应用场景:(滚动条加载更多,鼠标的mouseover)