年底往往工作不好找, 由于某些原因提前换公司, 最近面试半个月包含有赞等公司, 把一些面试题目分享一下, 偏基础底层. 仅供参考, 如有错误或者改进, 可以在评论或者博客 https://www.yuanziwen.cn 获取联系方式, 谢谢指正.
- .aaa{
- font-size:20px;
- background:red;
- }
- .bbb{
- font-size:20px;
- background:yellow;
- float:left;
- }
- .ccc{
- font-size:40px;
- background:blue;
- float:left;
- }
- .ddd{
- font-size:20px;
- background:orange;
- }
- <body>
- <div class="aaa">
- aaa
- <div class="bbb">bbb</div>
- <div class="ccc">ccc</div>
- <div class="ddd">ddd</div>
- </div>
- </body>
画出 HTML 图
aaa 被浮动影响所覆盖, 但是 bbb 属于 aaa 的子元素, 只会产生 float 文字围绕的效果, 所以 bbb 和 ccc 会向左靠, aaa 和 ddd 并列的原因是因为 ccc 的 font-size:40px, 占据了第二行 ddd 的位置, 并且有浮动属性, 不会让后面的文字往前靠, 所以才会产生这种效果.
页面元素垂直水平居中有哪几种方式
- // 绝对定位
- .box{
- width: 200px;
- height: 200px;
- margin: auto;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- }
- // css3 + 绝对定位
- .box{
- position:absolute;
- top:50%;
- left:50%;
- transform: translate(-50%,-50%);
- }
- // flex
- .box{
- display: flex;
- justify-content: center; /* 水平居中 */
- align-items: center; /* 垂直居中 */
- }
- // table
- .box{
- display:table-cell;
- text-align:center;
- vertical-align:middle;
- }
平常用到的就这些了
CSS 优化提高性能的方法有哪些
这个有很多优化的地方, 比如资源加载方面, 渲染性能, 解析及回流方面都有所涉及, 移除空的元素选择器
CSS 资源托管 CDN, 避免并发加载上限, 依赖加载, 不让页面加载延后资源的时候进行重绘.
JS 文件内尽量少修改首屏 CSS 样式, 引发重绘与回流. 尽量去创建图层避免多次回流, 减少回流的次数.
用 translate 替代 top 改变
用 opacity 替代 visibility
不滥用浮动, web 字体
不滥用无效属性, 比如使用了 display:inline; 就不要再设置 width,height 等属性
值为 0 时不需要任何单位
尽量少用标签选择器, 因为浏览器是从最后的选择开始往前做匹配的
使用 CSS 特性样式的时候加上浏览器前缀
不要一条一条地修改 DOM 的样式, 预先定义好 class, 然后修改 DOM 的 className 把 DOM 隐藏后修改, 比如: 先把 DOM 给 display:none (有一次 Reflow), 然后你修改 100 次, 然后再把它显示出来
不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
尽量不要使用 table 布局, 可能很小的一个小改动会造成整个 table 的重新布局
新建图层实现动画效果
启用 GPU 硬件加速
max-height:0px 和 height:200px !important; 谁生效
min 和 max 无视! important; 取前者
JS 篇
怎么去手写实现一个 new 方法 以及 new 到底做了什么
这个问题有一半公司都问到了, 可能是考基础和作用域链吧
- function _new(fun) {
- return function() {
- let obj = {
- __proto__: fun.prototype
- }
- fun.apply(obj, arguments)
- return obj
- }
- }
new 运算符怎么从构造器中得到一个对象
在《JavaScript 设计模式和开发实践一书》中, 我们可以这么去理解 new 运算的过程
- var objectFactory = function(){
- var obj = new Object();// 创建一个 Object
- var Constructor = [].shift.call(arguments);// 获取指定函数对象
- obj.__proto__ = Constructor.prototype;//Object.prototype 是个错误的指向, 所以改变指向的原型
- var ret = Constructor.apply(obj, arguments);// 借用外部传入的构造器给 obj 设置属性
- return typeof ret === 'object'? ret : obj;// 兼容, 因为某些浏览器没有暴露__proto__属性.
- }
- // 测试
- function Person(name){
- this.name = name;
- }
- Person.prototype.getName = function(){
- return this.name;
- }
- var a = objectFactory(Person,'seven');
- console.log(a.name);//seven
- console.log(a.getName());//seven
- console.log(Object.getPrototypeOf( a ) === Person.prototype);//true
以下代码运行结果
- var myName1 = function(){
- this.name = 'seven';
- return {
- name:'juejin'
- }
- }
- var myName2 = function(){
- this.name = 'seven';
- return 'juejin'
- }
- var myName3 = function(){
- return 'juejin'
- }
- var obj = new myName1();
- console.log(obj.name);//juejin
- var str = new myName2();
- console.log(str.name);//seven
- var und = new myName3();
- console.log(und.name);//undefined
题不难, 关键在 new 需要注意的一个问题:
如果构造器显示的返回了一个 object 类型的对象, 那么此次运算结果会最终返回这个对象, 而不是我们之前期待的 this
当引用一个变量的时候, 引擎做了那些事情
例如:
- var obj = {
- name: 'seven'
- };
- var A = function(){
- };
- A.prototype = obj();
- var B = function(){
- };
- B.prototype = new A();
- var b = new B();
- console.log(b.name);//seven
首先引擎根据 LHS 查询去 getter 遍历 b 的所有属性, 没有找到
查找 name 属性的请求被委托给了对象 b 的构造器的原型, b.__proto__被指向 B.prototype
而 B.prototype 又被指向 new A() 创建出来的对象, 在该对象中仍然没有找到
然后再往上委托给 A.prototype, 而它被设置成 obj
在 obj 中 getter 到 name 属性, 返回该值
题外: 如果最后 obj 也没有找到 name 属性, 请求将会传递给 obj 的构造器原型: Object.prototype, 而 null 作为对象原型链的终点. 显然没有, 返回 undefined
怎么手写实现 Object.create()方法
Object.create 方法克隆一个对象, 把__proto__改变指向, prototype 被 new 之后才会产生__proto__所以得出方法:
- function create(target){
- var F = function(){};
- F.prototype = target;
- return new F()
- }
- var obj = {name : 'seven'};
- var copy = create(obj);
- console.log(copy.name); //seven
手动实现 bind
简化版
- Function.prototype.myBind = function(context){
- var _this = this;// 保存指向
- return function(){
- return _this.apply(context,arguments);// 改变 this 执行对象
- }
- }
当然, 为了两次参数结合, 还需要复杂些
- Function.prototype.myBind = function(){
- var _this = this;
- var context = [].shift.call(arguments);// 获取目标对象
- var args = [].slice.call(arguments);// 获取剩余参数
- return function(){
- return _this.apply(context,[].concat.call(args,[].slice.call(arguments)));
- //context 改变指向, 而后面是为了整合两次传入的参数
- }
- }
- var obj = { name: 'seven' }
- var fun = function(a,b,c){
- console.log(this.name)
- console.log(a,b,c)
- }.bind(obj,1,2)
- fun(3);
- //seven
- //1,2,3
JavaScript 中的原型, 原型链是什么?
每一个函数都有 prototype 属性, 它指向一个对象, 即原型对象. 其他对象可以通过它实现属性继承, 而且任何一个对象都可以成为原型.
每个对象都有__proto__属性, 它是实现原型链的的关键, 而 prototype 则是原型链的组成, 任何被实例出的属性都会通过它去调用父类原型的方法, 父类如果没有方法就会再去通过父类的父类的 prototype 里找, 直到 Object.prototype,null 作为对象原型链的终点, 没有找到, 返回 undefined.
- function fn () {
- this.name = 'seven'
- }
- fn.prototype.getName = function(){
- console.log(this.name)
- }
- var fn2 = new fn();
- fn2.getName();//'seven';
- fn2.__proto__ === fn.prototype;// true
作用域和执行上下文的区别
作用域是函数定义时确定的, 函数内的变量根据变量去一层一层网上查找. 上下文是函数调用时确定的, 主要是 this 的值, 简单来说, 谁调用它就指向谁.
为什么 typeof null 是 object
因为在二进制里, 对象的前三位 000, 而 null 的二进制表示全 0, 自然前三位也是 0, 所以执行 typeof 时会返回 "object", 这是 ES5 遗留的一个 bug.
闭包是什么, 作用和缺陷
由于垃圾回收机制, 所有局部作用域的变量在退出函数后, 这些局部变量没有标示失去引用, 都将被自动回收销毁.
闭包能够访问其他函数内的变量, 也可以封装变量, 把一些不需要暴露在全局的变量封装成 "私有变量".
缺陷其实几乎没有, 很多人以为会造成内存泄漏, 那只是使用不当才会造成的. 因为使用闭包的同时容易形成循环引用, 如果闭包的作用域中保存着一些 DOM 节点, 才有可能造成内存泄露, 由于 IE 的 BOM 和 DOM 中的对象是使用 C++ 以 COM 对象的方式实现的, 而 COM 对象的垃圾回收机制采用的是引用计数策略, 如果两个对象间造成了循环引用, 那这两个对象都无法被回收. 就得手动设置为 null, 然后下次垃圾收集器会删除它们.
代码输出结果
- function fn1(){
- for(var i=0;i<4;i++){
- var tc = setTimeout(function(i){
- console.log(i)
- clearTimeout(tc)
- },10,i)
- }
- }
- fn1();
看到这题的时候, 第一时间在想 clearTimeout(tc)是什么时候去执行的. 如果没有这句的时候. 输出肯定是 0,1,2,3, 但是问题要从 var tc 来看, tc 被重复赋值了, 而 clearTimeout 在异步函数里, 只清除了申明的最后一次. 所以最后输出 0,1,2
- function fn2(){
- for(var i=0;i<4;i++){
- var tc = setInterval(function(i,tc){
- console.log(i)
- clearTimeout(tc)
- },10,i,tc)
- }
- }
- fn2()
这个跟上一题也有相似之处, 不过 tc 被传进去了, i=0 的时候, 设置了定时器 a 执行, 当 i=1 的时候定义了定时器 b,a 已经执行了一次, 再所以会把上一次的值给清除掉, 导致每次都执行了上一次的定时器才 clearInterval 关闭掉, 最后一次是因为 i=4 的时候才能关掉 i=3 的定时器, 但是因为条件限制所以没关闭掉.
所以 fn2 会输出 0,1,2,3(无限循环)......
科里化
有如下一个函数
- var fn = function(a,b,c){
- return a+b+c;
- }
拟写一个函数满足 curry(fn)(1)(2)(3) 返回值为 6;
- function curry(fn) {
- if(typeof fn !== 'function') throw new Error("首个参数必须为函数");
- var len = fn.length;
- var arg = [].prototype.slice.call(arguments, 1);// 保存参数
- return function() {
- arg = arg.concat([].prototype.slice.call(arguments));// 参数合并
- if(arg.length <len) { // 不满足 a,b,c 3 个形参的长度, 继续调用
- return arguments.callee;
- }else{ // 满足条件, 执行语句
- return fn.apply(null,arg);
- }
- }
- }
- curry(fn)(1)(2)(3);//6
简单一点的例子 科里化函数的特点参数复用, 提前返回, 延迟计算 / 运行. 需要理解闭包的概念
- var adder = function () {
- var _value = 0;
- return function () {
- if (arguments.length === 0) { // 假如没有参数返回, 就返回值, 终止循环调用
- return _value
- }
- _value+=parseFloat([].slice.call(arguments))
- return arguments.callee;// 继续调用
- }
- };
- var arr = adder();
- arr(1)(2)(3)(); //6
HTML5 的离线储存怎么使用, 工作原理能不能介绍下
在 HTML 属性里加入
<HTML manifest = "cache.manifest">
配置文件
CACHE MANIFEST // 必须写
版本号
- #v1.0.0
- # 需要缓存的文件
- CACHE:
/reset.CSS
/App.JS
- # 不需要缓存的文件
- NETWORK:
/login.HTML
- /data/*
- # 无法访问页面
- FALLBACK:
- /404.HTML
基于新建的 cache.manifest 文件的缓存机制, 通过这个文件上的解析清单离线存储资源. 当网络在处于离线状态时浏览器自动取出离线存储的数据进行页面解析后展示.
如何判断一个对象是不是函数
- var fn = function () {
- }
- // typeof JQ 源码
- typeof fn === 'function' && typeof obj.nodeType !== "number";
- // toString
- Object.propotype.toString.call(fn) === "[object Function]"
- // 通过原型
- fn instanceof Function
- // 通过构造函数
- fn.constructor === Function
列几条 JavaScript 的基本规范
使用全等 ===/ 全不等!== 来比对数据类型
当命名对象, 函数和实例时使用驼峰命名规则
不要使用全局变量申明, 必须加 var, 避免污染全局命名空间
当命名对象, 函数和实例时使用驼峰命名规则
还一堆....
pop()-push()-shift()-unshift()功能, forEach()-map()-reduce()有什么区别
pop(): 截取数组的最后一个元素, 返回该值
push(): 给数组的最后添加一个元素, 返回新长度
shift(): 截取数组的首个元素, 返回
unshift(): 给数组的首个位置添加一个元素, 返回新长度
reduce(): 不改变原数组, 接受一个函数作为累加器, 数组长度为 0 时不执行, 适合计算大批数据
map(): 不影响原数组, 返回新的数组
forEach(): 改变原数组做处理
["1","2","3"].map(parseInt) 输出什么;
因为 map 会有 2 个参数, 第一个是 item 对象, 第二个是索引, 而 parseInt 误将第二个参数认为是解析的基数, 导致输出[1,NaN,Nan]
["1","2","3"].map(parseInt)
等同于
- ["1","2","3"].map(function(item,index){
- return parseInt(item,index)
- })
浏览器和其他考点
一个页面, 从输入 URL 到页面加载完成发生了什么(越详细越好)
我回答的不够仔细, 以下是针对收集的资料整合之后的
输入 URL, 浏览器开启一个线程来处理这个请求, 对 URL 进行协议判断, 如果是 http 就按照 Web 方式处理
在浏览器缓存, 系统缓存, 路由器缓存里查找, 有就直接返回.
通过 DNS 解析拿到真实的 IP 地址.
向真实的 IP 地址服务器发起 TCP 连接, 与浏览器建立 tcp 三次握手.
进行 HTTP 协议会话, 浏览器发送报头(请求报头)
服务器处理请求将结果 (资源文件) 返回至浏览器
浏览器下载 HTML 文档, 同时设置缓存;
浏览器启用 HTML Parse 解析器对 HTML 文件进行解析, 通过词法分析的过程, 将内容分析成不同的 token, 然后根据 HTML 的文档, 从上到下依次 nextToken 进行解析 token, 并获取下一个 token 的位置, 所以我们的 DOM tree 是通过词法分析 token 一步一步添加的. 而类似于 link,script src 引用 Web 资源的地址由浏览器发送请求 CSS 和 JS 相关资源, 将请求回来的 JS 资源利用 V8 内核引擎执行 JS 代码. 请求回来的 CSS 资源则会生成相应的 CSSOM, 前面的 DOM tree 生成完毕后浏览器并不会直接渲染出来, 而是会等待
CSSOM(CSS tree)
生成后进行合并, 再渲染出来. 形成绘制树 Render Tree.
最后计算每个结点在页面中的位置, 这一个过程称为 layout 其过程是在一个连续的二维平面上进行的, 接下来将这些结果栅格化, 映射到屏幕的离散二维平面上, 这一过程称为 paint; 现代浏览器为提升性能, 将页面划分多个 layer, 各自进行 paint 然后组合成一个页面呈现在用户眼前
线程和进程的区别
线程是最小的执行单元, 进程是最小的资源管理单元. 一个线程只能属于一个进程, 而一个进程可以有多个线程, 至少有一个线程
请描述你对 HTTP 协议的理解
HTTP 是一个属于面向应用层的超文本传输协议, 基于 TCP/IP 通信协议来传递数据, 制定服务器与客户端之间数据传输的通信规则. HTTP 有 1.0 和 1.1,2.0 版本, 保持着无状态, 无连接的协议. HTTP 请求由三部分组成: 请求行, 消息报头, 请求正文. HTTP 响应也是由三个部分组成, 分别是: 状态行, 消息报头, 响应正文. 剩余的我也不知道该说什么了
请描述你对跨域的理解 (同源限制) 是什么, 解决办法
浏览器的同源策略导致了跨域用于隔离潜在恶意文件的重要安全机制, 限制必须协议, 域名, 端口一致
既然前端有限制, 就把请求代理转移到后端. 反向代理, 需要服务端做大量修改, 不建议;
由于浏览器不会阻止动态文本加载, 所以 JSONP 方式应运而生
CORS, 虽然比代理简单一些, 但是不会自动处理响应头(cookie 等...), 只需要服务端设置
Access-Control-Allow-Origin
和
Access-Control-Allow-Methods
等属性进行设置.
iframe 嵌套通讯, 通过 postMessage 和 message 事件通信, 用的较少
WebSocket, 也需要服务器做相关设置, 像 Node 的 socket.io
请说明 sessionStorage,localStorage,cookie 的区别
local: 本地存储, 窗口通用, 生命周期是永久, 除非用户手动清除.
session: 会话储存, 限定本次窗口, 关闭后消失.
本地存储 Storage 的大小限制 5MB, 不参与通信, 遵守同源限制.
cookie: 每个 domain 最多只能有 20 条 cookie, 每个 cookie 长度不能超过 4KB. 否则会被截掉. 每次请求都会自动带上.
什么是 cookie 隔离
cookie 在访问对应域名下的资源时都会通过 HTTP 请求发送到服务器. 但是访问静态资源根本不需要 cookie, 否则非常浪费流量, 可以使用不同的 domain(域名)来存储资源. 因为 cookie 有跨域限制不能跨域提交请求, 使用其他域名的请求头中就不会带有 cookie 数据, 降低请求头的大小和请求时间, 也减少了 Web Server 对 cookie 的处理分析环节, 提高了 webserver 的 http 请求的解析速度, 从而提高响应速度.
其他
问的最多的就是开发部署流程还有框架实现原理, 笔者主 vue, 建议去看 vue 源码实现, 有 render,update,patch,vnode 等相关的解读, vue3.0 明年就要见面了. 开发部署流程每个公司有其自己的规范, 这里就不提了.
高中学历工作一年半不到, 一些细节的东西可能不懂或者有描述不对的地方, 欢迎指正.
今天就跨年了, 最后希望大家元旦快乐!!!
来源: https://juejin.im/post/5c204cd85188254351476275