1.函数柯里化的实现
// 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
function curry(fn, args) {
// 获取函数需要的参数长度
let length = fn.length;
args = args || [];
return function() {
let subArgs = args.slice(0);
// 拼接得到现有的所有参数
for (let i = 0; i < arguments.length; i++) {
subArgs.push(arguments[i]);
}
// 判断参数的长度是否已经满足函数所需参数的长度
if (subArgs.length >= length) {
// 如果满足,执行函数
return fn.apply(this, subArgs);
} else {
// 如果不满足,递归返回科里化的函数,等待参数的传入
return curry.call(this, fn, subArgs);
}
};
}
// es6 实现
function curry(fn, ...args) {
return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);
}
2.js 中的深浅拷贝实现?
浅拷贝
指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用 Object.assign 和展开运算符来实现。
深拷贝
相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败。
// 浅拷贝的实现;
function shallowCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
// 深拷贝的实现;
function deepCopy(object) {
if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] =
typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
return newObject;
}
3.js的事件循环是什么?
因为 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。
宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲
染等。
4.Unicode 和 UTF-8 之间的关系?
unicode 是一种字符集合,现在可容纳100多万个字符。。每个字符对应一个不同的unicode 编码,他只规定这个二进制代码在计算机中如何编码传输。
utf-8 是一种对 unicode 的编码方式,他是一种变长的编码方式,可以用1-4个字节来表示一个字符
5.Object.is() 与原来的比较操作符 “=”、“” 的区别?
1.使用双等号进行相等判断,如果两本的类型不一致,则会进行强制 类型转化后在进行比较。
2.使用三等号进行相等判断时,如果两本的类型不一致时,不会做强制类型转换,直接返回false。
3.使用object.is 来进行相等判断时,一般情况下和三等号相同,他处理一些特殊的情况,比如-0 和+0 不在相等,两个NaN 认定为相等
6.介绍一下 js 的节流与防抖?
函数防抖是指在事件被触发 n 秒后在执行回调,如果在这n秒内事件又被触发,则重新计时。这样可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
函数节流是规定一个单位时间,在这个单位时间内,只能触发一次事件的回调,如果在此期间某事件被触发多次,只能一次能生效。节流可以使用在scroll函数的事件监听上,通过事件节流来降低事件调用的频率。
// 函数防抖的实现
function debounce(fn, wait) {
var timer = null;
return function() {
var context = this,
args = arguments;
// 如果此时存在定时器的话,则取消之前的定时器重新记时
if (timer) {
clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
// 函数节流的实现;
function throttle(fn, delay) {
var preTime = Date.now();
return function() {
var context = this,
args = arguments,
nowTime = Date.now();
// 如果两次时间间隔超过了指定时间,则执行函数。
if (nowTime - preTime >= delay) {
preTime = Date.now();
return fn.apply(context, args);
}
};
}
7.使用js 实现获取文件扩展名
// String.lastIndexOf() 方法返回指定值(本例中的'.')在调用该方法的字符串中最后出现的位置,如果没找到则返回 -1。
// 对于 'filename' 和 '.hiddenfile' ,lastIndexOf 的返回值分别为 0 和 -1 无符号右移操作符(>>>) 将 -1 转换为 4294967295 ,将 -2 转换为 4294967294 ,这个方法可以保证边缘情况时文件名不变。
// String.prototype.slice() 从上面计算的索引处提取文件的扩展名。如果索引比文件名的长度大,结果为""。
function getFileExtension(filename) {
return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2);
}
8.检测浏览器版本有哪些方式?
1.一种是检测 window.navigator.userAgent 的值 但这种方法 因为userAgent 可以被改写,并且早期的浏览器ie 会通过伪装资金的userAgent 的值为 Mozilla 来躲过服务器的检测
2. 功能检测 可以根据每个浏览器的特性来判断,如ie 下独有的Activexobject。
9.移动端的点击事件有延迟 时间是多久 ,为什么会有 怎么解决这样延迟?
移动端的点击事件有300ms的延迟,是因为移动端会有双击缩放的这个操作,因此浏览器在click 之后要等待300ms,看用户有没有下一次点击,来判断这次操作是不是双击。
有三种方法解决
通过meta标签禁用网页的缩放
通过meta的标签将网页的biewport 设置为 ideal viewport
调用一些js库,比如 FastClick
10.如何编写高性能的JavaScript?
- 使用位运算代替一些简单的四则运算
- 避免使用过深的嵌套循环
- 不要使用未定义的变量
- 当需要多次访问数组长度时,可以用变量保存起来 避免每次都会去进行属性查找
11.JavaScript 中的作用域与变量声明提升?
变量提升的表现是,无论我们在函数中何处位置声明的变量,好像都被提升到了函数的首部,我们可以在变量声明前访问到而不会报错。
造成变量提升的本质原因是js引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当我们访问一个变量时,我们会到当前执行上下文中的作用域链中去查找,而作用域链的手端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参,所有的函数和变量声明,这个对象的是在代码解析的时候创建的。这就是出现变量声明提升的根本原因。
12 数组的fill方法
fill()方法用一个固定值填充一个数组中从起始索引内的全部元素。不包括终止索引。
fill方法接受三个参数 value ,start以及end,start和end 参数是可选的 ,其默认值分别为0 和this对象的length属性值
13.数组和对象有哪些原生方法,列举一下?
数组和字符串的转换方法:toString()、toLocalString()、join()其中join()方法可以指定转换为字符串
数组尾部操作的方法 pop()和push() push()方法可以传入多个参数
数组的首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和sort(),sort()方法可以传入一个函数进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置
数组的连接方法 concat() 返回的是拼接好的数组 不影响原数组
数组的截取办法 slice() 用于截取数组中的一部分返回,不影响原数组。
数组插入方法 splice() 影响原数组查找特定项的索引方法,indexOf()和lastIndexOf()
迭代方法 every() some() filter() map() forEach()
数组归并方法 reduce() reduceRight()
14.类数组的定义
一个拥有 length属性 和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法
常见的类数组对象有 arguments 和DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它包含length属性值 代表可接受的参数个数
常见的类数组转换为数组的方法有这样几种:
- 通过call调用数组的slice 方法来实现转换
Array.prototype.slice.call(arrayLike)
2.通过call调用数组的splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0)
3.通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([],arrayLike);
4.通过 Array.from 方法来实现转换
rray.from(arrayLike);
15.call()和apply()的区别
他们的作用一模一样 区别仅在于传入参数的形式不同
apply 接受两个参数,第一个参数制定了函数体内的this对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组 也可以为类数组 apply方法把这个集合中的元素作为参数传递给被调用的函数
call 传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,第二个参数开始往后,每个参数被依次传入函数。
16.innerHTML 与 outerHTML 的区别?
//对于这样一个 HTML 元素:<div>content<br/></div>。
innerHTML:内部 HTML,content<br/>;
outerHTML:外部 HTML,<div>content<br/></div>;
innerText:内部文本,content ;
outerText:内部文本,content ;
17.DOM操作 – 怎样添加、移除、移动 、复制 、创建 和查找节点?
创建新节点
createDocumentFragment();
createElement(node);
createTextNode(text);
添加、移除、替换、插入
appendChild();
removeChild()
replaceChild(new,old)
insertBefore(new,old)
查找
getElementById();
getElementsByName();
getElementsByTagName();
getElementsByClassName();
querySelector();
querySelectorAll();
属性操作
getAttribute(key)
setAttribute(key,value)
hasAttribute(key)
removeAttribute(key)
18.requireJS 的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何 缓存的?)
require.js 的核心原理是通过动态创建 script 脚本来异步引入模块,然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数。
19.简单谈一下 cookie ?
cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地,当下一次有同源的请求时,将保存的cookie值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。cookie 一般可以存储 4k 大小的数据,并且只能够被同源的网页所共享访问。
服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的时间,domain 是域名、path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输,HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。
在发生 xhr 的跨域请求的时候,即使是同源下的 cookie,也不会被自动添加到请求头部,除非显示地规定。
20. 什么是浏览器的同源政策?、
我对浏览器的同源政策的理解是,一个域下的 js 脚本在未经允许的情况下,不能够访问另一个域的内容。这里的同源的指的是两个
域的协议、域名、端口号必须相同,否则则不属于同一个域。
同源政策主要限制了三个方面
第一个是当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。
第二个是当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。
第三个是当前域下 ajax 无法发送跨域请求。
同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者
script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。