-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
一些个面试题与js知识点 #69
Comments
/*
* 经典面试题
* 函数参数不定回调函数数目不定
* 编写函数实现:
* add(1,2,3,4,5)==15
* add(1,2)(3,4)(5)==15
*/
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = [].slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var adder = function () {
var _adder = function() {
[].push.apply(_args, [].slice.call(arguments));
return _adder;
};
// 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
return adder.apply(null, _args);
}
// 输出结果,可自由组合的参数
console.log(add(1, 2, 3, 4, 5)); // 15
console.log(add(1, 2, 3, 4)(5)); // 15
console.log(add(1)(2)(3)(4)(5)); // 15 |
function Parent() {
this.a = 1;
this.b = [1, 2, this.a];
this.c = { demo: 5 };
this.show = function () {
console.log(this.a , this.b , this.c.demo );
}
}
function Child() {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
}
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show(); |
(function () {
var x,y; // 外部变量提升
try {
throw new Error();
} catch (x/* 内部的x */) {
x = 1; //内部的x,和上面声明的x不是一回事!!
y = 2; //内部没有声明,作用域链向上找,外面的y
console.log(x); //当然是1
}
console.log(x); //只声明,未赋值,undefined
console.log(y); //就是2了
})();
// 1
// undefined
// 2 |
js事件循环
function testSometing() {
console.log("执行testSometing");
return "testSometing";
}
async function testAsync() {
console.log("执行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing();//关键点1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}
test();
var promise = new Promise((resolve)=> { console.log("promise start.."); resolve("promise");});//关键点2
promise.then((val)=> console.log(val));
console.log("test end...")
// VM256:12 test start...
// VM256:2 执行testSometing
// VM256:22 promise start..
// VM256:25 test end...
// VM256:14 testSometing
// VM256:7 执行testAsync
// VM256:23 promise
// VM256:16 hello async
// VM256:17 testSometing hello async |
性能优化速记
webkit 主资源与派生资源:主资源,比如 HTML 页面,或者下载项,一类是派生资源,比如 HTML 页面中内嵌的图片或者脚本链接 200 from memory cache不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当kill进程后,也就是浏览器关闭以后,数据将不存在。仅派生资源(缓存 js脚本文件,css样式表文件,font字体文件,图片文件等静态文件) 200 from disk cache不访问服务器,直接读缓存,从磁盘中读取缓存,当kill进程时,数据还是存在。这种方式也只能缓存派生资源 304 Not Modified访问服务器,服务器返回此状态码表示资源仍可用, 然后从缓存中读取数据 三级缓存原理先去内存看,如果有,直接加载 所以我们可以来解释这个现象 ,图片为例: 访问-> 200 -> 退出浏览器
|
cookie 相关cookie一般用于保存信息,你向同一个服务器发请求时会带上浏览器保存的对于那个服务器的cookie,而不管你从哪个网站发请求。所以如果用cookie校验权限则会导致csrf攻击的成立, 即 当你在当前网站(A网站)登录后, 浏览第三方页面(伪造网站等), 在第三方页面上发起对A网站的请求 但目前一般权限校验采用JWT,请求头Authoritarian传递token校验用户权限,规避cookie自动携带的隐患 再就是目前 cookie 再就是第三方cookie Third-party cookies
如果大多网站都有嵌入类似 阿里妈妈, 百度分析,google分析,faceboo广告等第三方请求(js,跨域请求,链接等), 则 |
浏览器相关渲染相关
DOM 和 CSSOM通常是并行构建的,所以 「CSS 加载不会阻塞 DOM 的解析」。 Load 和 DOMContentLoaded 区别Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。 DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载 常见状态码
浏览器缓存, 强缓存,协商缓存
安全机制
事件触发三阶段捕获 -> 目标 -> 冒泡 |
vue 响应式原理:简要实现双向事件绑定:
监听器 Observer 实现 /**
* 循环遍历数据对象的每个属性
*/
function observable(obj) {
if (!obj || typeof obj !== 'object') {
return;
}
let keys = Object.keys(obj);
keys.forEach((key) => {
defineReactive(obj, key, obj[key])
})
return obj;
}
/**
* 将对象的属性用 Object.defineProperty() 进行设置
*/
defineReactive: function(data, key, val) {
var dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function getter () {
// 收集依赖
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set: function setter (newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 通知订阅者
dep.notify();
}
});
} 消息订阅器 function Dep () {
// 储存订阅者 watcher
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null; 订阅者 function Watcher(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
this.run();
// 实际中会区分更新是 同步还是异步的
// 异步的话 执行异步watcher队列 queueWatcher(this)
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 全局变量 订阅者 赋值
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数,使dep收集依赖
Dep.target = null; // 全局变量 订阅者 释放
return value;
}
}; 订阅者 Watcher 分析如下:订阅者
当我们去实例化一个渲染
实际上就是把 // 强制执行响应对象的get函数, 使dep收集Dep.target 即当前watcher
let value = this.vm.data[this.exp] 在这个过程中会对 每个对象值的 这样实际上已经完成了一个依赖收集的过程。那么到这里就结束了吗?其实并没有,完成依赖收集后,还需要把
而 解析器 Compile 关键逻辑代码分析 通过监听器 解析器
我们下面对 compileText: function(node, exp) {
var self = this;
var initText = this.vm[exp]; // 获取属性值
this.updateText(node, initText); // dom 更新节点文本值
// 将这个指令初始化为一个订阅者,后续 exp 改变时,就会触发这个更新回调,从而更新视图
new Watcher(this.vm, exp, function (value) {
self.updateText(node, value);
});
} 简单理解为:
vue nexttick 原理:<template>
<div class="box">{{msg}}</div>
</template>
export default {
name: 'index',
data () {
return {
msg: 'hello'
}
},
mounted () {
this.msg = 'world'
let box = document.getElementsByClassName('box')[0]
console.log(box.innerHTML) // hello
}
} 以上代码可以看到,修改数据后dom并没有立刻更新,vue中dom的更新机制是异步的,(会对异步更新队列中的 this.msg = 'world'
let box = document.getElementsByClassName('box')[0]
// 如果我们需要获取数据更新后的dom信息,
// 比如动态获取宽高、位置信息等,需要使用nextTick
this.$nextTick(() => {
console.log(box.innerHTML) // world
}) 解析:双向绑定原理: // watcher update
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
// dom操作 执行异步队列 queueWatcher
queueWatcher(this)
}
}
function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
// 通过waiting 保证nextTick只执行一次
waiting = true
// 最终queueWatcher 方法会把flushSchedulerQueue 传入到nextTick中执行
nextTick(flushSchedulerQueue)
}
}
} 其中 function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
...
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
// 遍历执行渲染watcher的run方法 完成视图更新
watcher.run()
}
// 重置waiting变量
resetSchedulerState()
...
} 当数据变化最终会把
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// push进callbacks数组
callbacks.push(() => {
cb.call(ctx)
})
if (!pending) {
pending = true
// 执行timerFunc方法
timerFunc()
}
}
let timerFunc
// 判断是否原生支持Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
// 如果原生支持Promise 用Promise执行flushCallbacks
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
// 判断是否原生支持MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
// 如果原生支持MutationObserver 用MutationObserver执行flushCallbacks
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
// 判断是否原生支持setImmediate
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
// 如果原生支持setImmediate 用setImmediate执行flushCallbacks
setImmediate(flushCallbacks)
}
// 都不支持的情况下使用setTimeout 0
} else {
timerFunc = () => {
// 使用setTimeout执行flushCallbacks
setTimeout(flushCallbacks, 0)
}
}
// flushCallbacks 最终执行nextTick 方法传进来的回调函数
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
} 响应式对象设置属性值 响应式触发 之后用户再使用vue.nextTick(callback)则可确保回调函数在dom跟新后执行 由于宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,再使用宏任务
注意不要把“UI渲染”跟“DOM更新”这2个混为一谈了,“UI渲染”确实是所有微任务完成之后,是异步的。而“DOM更新”是同步的(程序中),只不过用户想在页面观察到变化需要等待UI渲染之后 vue-vuex中使用commit提交mutation来修改state的源码解析
vuex 设置严格模式参数为true, 调用了 if(process.env.NODE_ENV !== 'production'){
assert(store._committing,`Do not mutate vuex store state outside mutation handlers.`)
} 通过 虽然直接修改 |
重绘与重排重新渲染,就需要重新生成布局和重新绘制。前者叫做重排(reflow 或 回流),后者叫做重绘(repaint) 需要注意的是,重绘不一定需要重排,重排必然导致重绘。为了提高网页性能,就要降低"重排"和"重绘"的频率和成本,尽量少触发重新渲染 浏览器为了重新渲染部分或整个页面,重新计算页面元素位置和几何结构的进程叫做
一些常用且会导致回流的属性和方法:
当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以上属性或者使用以上方法,以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来 触发回流的时候,如果 DOM 结构发生改变,则重新渲染 DOM 树,然后将后面的流程(包括主线程之外的任务)全部走一遍 当页面中元素样式的改变并不影响它在文档流中的位置时(例如: 于没有导致 DOM 几何属性的变化,因此元素的位置信息不需要更新,从而省去布局的过程,流程如下: 跳过了 合成还有一种情况:就是更改了一个既不要布局也不要绘制的属性,那么渲染引擎会跳过布局和绘制,直接执行后续的合成操作,这个过程就叫合成。 举个例子:比如使用CSS的
提升合成层的最好方式是使用 CSS 的 css3的translate会引起重排吗, 并不会,不是同一个复合图层
各种height, width |
转自前端进阶
MS
ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数。 被传递的值会被复制给一个局部变量(arguments)
js slice 与 splice
字符串回文与匹配 与乱序
this
引用的是函数据以执行的环境对象——或者也可以说是 this 值(当在网页的全局作用域中调用函数时, this 对象引用的就是 window)。运算符优先级表
优先级 高到低 同级不同运算从左到右
20 圆括号
19 成员访问
.
19 需计算的成员访问
[]
19
new
(带参数列表 例: new...(...) )19 函数调用 例: ...(...)
18
new
(无参数列表) 例: new ...17 后置自增,后置自减
a++
a--
...
(new foo 等同于 new foo(), 只能用在不传递任何参数的情况)
函数声明提升 和 变量声明提升
变量提升也有优先级, 函数声明 > arguments > 变量声明
函数作用域链包含两个对象:的作用域链包含两个对象:它自己的变量对象(其中 定义着 arguments 对象) 和 全局环境的变量对象。
作用域链是定死的,函数引用的变量在哪里定义,引用的就是哪里的变量.
函数的隐式转换
对象通过valueOf方法,把自己转换成数字,通过toString方法,把自己转换成字符串
如果字符串和数字相加,JavaScript会自动把数字转换成字符的,不管数字在前还是字符串在前,字符串和数字相加结果是字符串
一个对象同时存在valueOf方法和toString方法,那么,valueOf方法总是会被优先调用的
参考
==
与===
与Object.is()
获取页面用到哪些元素
document.getElementsByTagName('*');
document.all
能取得当前页面所有的element,判断nodeType===1
就是element了,取nodeName
就是标签名称js 中的
new
访问属性对象
.
与[]
[]
中可以用变量this
函数调用可等价转化为call形式;
数组去重
模拟call , apply函数
参考call,apply,bind
模拟实现
模拟bind函数
bind方法
返回类型
The text was updated successfully, but these errors were encountered: