无用的面试题

Posted by Turingdo Studio on December 22, 2019

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)对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

快速排序的javascript实现

共同点:用于浏览器端存储的缓存数据

不同点:

(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个步骤,实现数据的双向绑定:

  1. 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  2. 实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

  3. 实现一个解析器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

  1. 传入promise中的方法会被立即执行

  2. then方法会缓存传入的函数,但不会立即执行

  3. 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);
}

防抖函数与节流函数

  1. 函数防抖和函数节流都是防止某一时间频繁触发,但是这两兄弟之间的原理却不一样。
  2. 函数防抖是某一段时间内只执行一次,而函数节流是间隔时间执行。 防抖的应用场景:(输入框请求api, 窗口的resize) 节流的应用场景:(滚动条加载更多,鼠标的mouseover)