前端面试题

css相关

1. margin和padding有什么不同?
作用对象不同。padding是作用于自身的,margin是作用于外面的。

2. vw与百分比有什么区别?
百分比有继承关系
vw只和设备(屏幕)的宽度有关系。

3. 行内元素与块级元素的区别
行内元素不换行,不可以设置大小,大小由内容决定
块级元素的宽度默认继承父级元素的宽度

4. 如何让谷歌浏览器支持小字体?(谷歌支持最小字体是12px,)
使用:transform: scale(0.8); -webkit-transform: scale(0.8);

js相关

1. let和var区别
声明提升、没有块级作用于、声明覆盖

2. 手写深拷贝
function deepClone (obj) {
    // A instanceof B   B的prototype是否在A的原型链上
    if (obj === null) return obj;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== 'object') return obj; 

    // 构造器最大的指向 [] => Array(基类)  {} => Object
    // constructor属性引用了该对象的构造函数.对于Object对象,该指针指向原始的 Object() 函数
    // 只有对象才有constructor, {}的constructor还是{},[]的constructor还是[]
    const newObj = new obj.constructor();

    for (let keys in obj) {
        // 检测属性是否为对象的自有属性
        if (obj.hasOwnProperty(keys)) {
            newObj[keys] = deepClone(obj[keys]);
        }
    }

    return newObj;
}

const person = {
    a: 1,
    b: [1, 2, 3],
    c: {
        d: 1,
        e: [1, 2]
    }
}
const newPerson = deepClone(person);

newPerson.a = 2;
newPerson.b[0] = 2;
console.log(person);
console.log(newPerson);

3. call apply bind的区别
回答:    1. bind不会立即执行函数,只会改变this指向,call和apply会立即实行函数且改变this指向
         2. call和apply的区别在于传参,call的传参是对象,apply传参是数组
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。

call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别,多个参数)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。

bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。

例子:fn.call({id: 1});  fn.apply([]);


4. 什么是闭包? 为什么要有闭包
    答:闭包就是能够读取其他函数内部变量的函数。创建闭包最常见方式,就是在一个函数内部创建另一个函数,且子函数引用了父函数的变量。

    a. 避免变量被污染
    b. 私有化 
    c. 保存变量,常驻内存

5. 闭包的应用场景
防抖,节流,库的封装(保证数据私有性)

6. new关键字在new的一瞬间做了什么(const person = new Person())?
    a. 创建一个空对象(let obj = new Object())
    b. 设置它的原型链(obj._proto_ = Person.prototype)
    c. 改变this指向(let result = Person.call(obj))
    d. 判断返回值类型 
    if (typeof result === 'object') {
        person = result;
    } else {
        person = obj;
    }

7. const obj = Object.create(null) 和 const obj2 = {}有什么区别?
obj没有_proto_, 是一个纯对象。 而obj2有原型

8. 普通函数和构造函数的返回值是什么?
普通函数返回一个undefined,构造函数返回新创建的对象

9. 什么是事件委托?
事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

10. 原生JS实现事件委托
<ul>
    <li>1</li>
    <li>2</li>
</ul>
<button id="btn">按钮</button>

let ul = document.getElementById('ul');
ul.onclick = function (event) {
    event = event || window.event;
    const target = event.target;

    // 规定就是大写
    if (target.nodeName == 'LI') {
        // 委托的事情
        alert(target.innerHTML);
    }
}

let btn = document.getElementById('btn');
btn.onclick = function () {
    let li = document.createElement('li');
    li.textContent = ul.children.length;
    ul.appendChild(li);
}

11. 手写Promise
function myPromise (excutor) {
    // 1. 执行结构
    let self = this;
    self.status = 'pending'; // 状态
    self.value = null; // 成功结果
    self.reason = null; // 失败原因

    // 8. 添加缓存数组
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];

    // 4. 判断状态
    // 成功
    function resolve (value) {
        if (self.status === 'pending') {
            self.value = value; // 保存成功结果
            self.status = 'fulfilled';

            // 10. 状态改变, 依次取出
            self.onFulfilledCallbacks.forEach(item => item(value));
         }
    }

    // 失败
    function reject (reason) {
        if (self.status === 'pending') {
            self.reason = reason; // 失败的原因
            self.status = 'rejected';

            // 10. 状态改变, 依次取出
            self.onRejectedCallbacks.forEach(item => item(reason));
        }
    }

    // 3. 执行一遍
    try {
        excutor(resolve, reject);
    } catch (err) {
        reject(err);
    }
}

// 2. then
myPromise.prototype.then = function (onFulfilled, onRejected) {
    // 5. 状态改变 => 调用then方法
    onFulfilled = typeof onFulfilled === 'function' ? 
    onFulfilled : function (data) {resolve(data)};

    onRejected = typeof onRejected === 'function' ? 
    onRejected : function (err) {throw err};

    let self = this;
    // // 9. 先添加进去
    // if (self.status === 'pending') {
    //     self.onFulfilledCallbacks.push(onFulfilled);
    //     self.onRejectedCallbacks.push(onRejected);
    // }

    if (self.status === 'fulfilled') {
        return new myPromise((resolve, reject) => {
            try {
                let x = onFulfilled(self.value);
                // 判断传进来的值是否是一个promise,是的话继续.then,否则抛出结果
                x instanceof myPromise ? x.then(resolve, reject) : resolve(x);
            } catch (err) {
                reject(err);
            }
        });
    }

    if (self.status === 'rejected') {
        return new myPromise((resolve, reject) => {
            try {
                let x = onRejected(self.value);
                x instanceof myPromise ? x.then(resolve, reject) : resolve(x);
            } catch (err) {
                reject(err);
            }
        })
    }

    if (self.status === 'pending') {
        return new myPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push(() => {
                let x = onFulfilled(self.value);
                x instanceof myPromise ? x.then(resolve, reject) : resolve(x)
            })
            self.onRejectedCallbacks.push(() => {
                let x = onRejected(self.value);
                x instanceof myPromise ? x.then(resolve, reject) : resolve(x);
            })
        })
    }
}

myPromise.prototype.catch = function (fn) {
    return this.then(null, fn);
}

let demo = new myPromise((resolve, reject) => {
    console.log('各位同学很帅!');

    // 6. 尝试异步
    setTimeout(() => {
        reject(11);
    }, 500);
});

// 7. 异步失败
demo.then(data => console.log(data))

12. 前端跨域问题(首先,跨域不是问题,是一种安全机制)
由于同源策略(协议http,域名localhost,端口号8080都要一致)导致的
可以使用nginx处理,node webpack都可以

一. 后端处理方案(node):cors
const express = require('express');
const app = express();
// req浏览器请求对象  res浏览器响应对象  
app.all('*', function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*'); // 允许所有请求来源
    res.header('Access-Control-Allow-Headers', '*'); // 允许所有请求头
    res.header('Access-Control-Allow-Methods', '*'); // 允许所有请求方法
    next(); // 交给下一层处理
});

二. 前端处理方案
// 脚手架代理 webpack处理 
webpack.config.js代码如下
// 脚手架配置
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.export = {
    mode: 'development',
    devServer: {
        // 自己配置脚手架代理 
        proxy: {
            // 加api目的的为了匹配代理
            '/api': {
                target: 'http:localhost:3000',
                pathRewrite: {'/api': ''} // 将'/api'替换成''
            },
            '/api1': {
                target: 'http:localhost:3000',
                pathRewrite: {'/api': ''} // 将'/api'替换成''
            }
        }
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'index.html'
        })
    ]
}

13. React的事件和原生标签的点击事件有什么区别?
React使用事件代理模式(绑定在根节点上),原生会绑定在具体的元素上,对性能不好













性能与网络相关面试题

1. TCP为什么需要三次握手?(TCP是全双工,客户端可以和服务端通信,服务端也可以和客户端通信)

答1(较正式回答):因为TCP是全双工,所以要经过 3 次交互才能确认双方的发送能力和接收能力,并且 TCP 握手必须是 3 次,如果是 2 次握手,不能证明服务器端的发送能力和客户端的接收能力(服务端不能确认客户端是否能接受到自己的信息);也不能是 4 次握手,因为 3 次已经能证明的事情,再交互握手 1 次完全没有必要。

答2(自总结回答):因为TCP是全双工,客户端需要确认服务端接收能力,服务端也要确认客户端的接收能力,如果只有2次握手,只能证明客户端发送能力和服务端接收能力,不能证明客户端接收能力,因此需要第三次握手确认。


2. TCP断开为什么要四次握手(四次挥手)?

因为TCP是双向对等传输,所有有两个方向的连接,需要两个FIN才能断开连接。
客户端向服务端发送FIN断开请求时,服务端回应ACK只是断开了客户端到服务端连接(服务端到客户端连接未断开),因此服务端还需要发FIN断开请求,客户端回复ACK则断开服务端到客户端连接。

3. 在浏览器中输入URL并回车后都发生了什么?

url -> DNS域名系统 -> 拿到真实IP -> 建立连接(TCP三次握手) -> 拿数据,渲染页面 -> 四次挥手

详细流程:
url: https://www.baidu.com
url => 统一资源定位符,俗称网址
url是IP的一个映射(比如https://www.baidu.com实际是去找192.168.x.x这种IP)
https:超文本传输协议(加密的传输协议,http和TCP之间加了一层TCL或者SSL的安全层)
www: 万维码(即服务器)
baidu.com:域名

第一次访问过程:
    解析url
    DNS域名系统匹配真实IP,下方是具体流程
    url -> DNS域名系统 -> 拿到真实IP -> 建立连接(TCP三次握手) -> 拿数据,渲染页面 -> 四次挥手
第二次访问:
    将域名解析的IP存在本地 => 读取浏览器缓存

4. 从哪些点做性能优化?

    a. 加载
        减少http请求(精灵图,文件的合并)
        减小文件大小(资源压缩,图片压缩,视频压缩,代码压缩)
        CDN(第三方库,大文件,大图)
        SSR服务端渲染,预渲染
        懒加载
        分包
    b. 减少dom操作,避免回流,文档碎片

回流(reflow):对于DOM结构中的各个元素都有自己的盒子模型,这些都需要浏览器根据各种样式(浏览器的、开发人员定义的等)来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow;

重绘(repaint):当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。

懒加载代码
<script>
    let num = document.getElementsByTagName('img').length;
    let img = document.getElementsByTagName('img');

    let n = 0;
    function lazyload() {
        // 可见区域
        let seeHeight = document.documentElement.clientHeight();
        // 滚动距离
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
        
        for (let i = n; i < num; i++) {
            if (img[i].offsetTop < seeHeight + scrollTop) {
                if (img[i].getAttibute("src") == "./img2/time.gif") {
                    img[i].src = img[i].getAttribute("data-src");
                }
                n = i + 1;
            }
        }
    }

    window.onscroll = lazyload();
</script>

5. 性能优化有哪些?
页面加载性能、动画与操作性能(首选translate和定位,脱离文档流对这个性能更好)、内存占用、电量消耗






笔试题

1. 
var length = 10;
function fn () {
    console.log(this.length); // window
}
var obj = {
    length: 6,
    method: function (fn) {
        console.log(this); // 这个this指向obj
        fn();
        arguments[0](); // arguments输出后是arguments[0]是fn,对象.函数,this指向对象,arguments的length是2,因此这里输出2
    }
}
obj.method(fn, 1);
答案:输出10,2(this指向上一个调用者,即对象.函数(), 函数的this就是指向对象)

2.
var j = {
    a: 10,
    b: {
        a: 12,
        fn: function () {
            console.log(this.a);
        }
    }
}
j.b.fn();
答案:12  (这个this指向他的上一级b)

3.
var id = 66;
function fn5() {
    setTimeout(() => {
        console.log(this.id);
    }, 500);
}
fn5({id: 22});
fn5.call({id: 22});
答案:66, 22(箭头函数没有作用域,所以没有this,这里的this指向上一层,所以this指向window)

4.
var length = 10;
function test () {
    console.log(this.length);
}

var obj = {
    length: 100,
    action: function (test) {
        test();
        arguments[0]();
    }
}

obj.action(test, 1, [2, 3], 4);
答案:10, 4

5.
var a = 10;
function test () {
    console.log(a);

    a = 100;
    console.log(this.a);

    var a;
    console.log(a);
}
test();

// 详细分析:作用域问题 es5: 预解析:var function () {}
var a;
function () {};
a = 10;
// 逐行解析
test();
// 预解析
var a;
console.log(a);
a = 100;
console.log(this.a);
console.log(a);
// 特别注意:如果全局变量和局部变量同名,全局变量是不会作用于局部变量的作用域

答案:undefined, 10, 100;

6.
var a = 10;
function test () {
    a = 100;
    console.log(a);

    a = 100;
    console.log(this.a);

    var a;
    console.log(a);
}
test();

答案:100, 10, 100;

7.
var a = 10;
function test () {
    console.log(a);

    a = 100;
    console.log(this.a);

    console.log(a);
}
test();

答案:10, 100, 100;

8.
var a = 10;
function f1 () {
    var b = 2 * a;
    var a = 20;
    var c = a + 1;
    console.log(b);
    console.log(c);
}
f1();

var a;
function f1 () {}
a = 10;
f1();
var b;
var a;
var c;
b = 2*a;
a = 20;
c = a + 1;
console.log(b); // NaN
console.log(c); // 21

答案:NaN,21







// Promise.all和Promise.race的区别
1. Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组(注意,返回的结果顺序与Promise.all接收的顺序是一样的,即使返回的结果更慢),而失败的时候返回最先被reject失败状态的值。

const res1 = new Promise((resolve, reject) => {
    resolve("成功1");
});
const res2 = new Promise((resolve, reject) => {
    resolve("成功2");
});
const res3 = new Promise((resolve, reject) => {
    reject("失败");
});
// 也可以这样写
// const res3 = Promise.reject("失败");

// 全部成功时
Promise.all([res1, res2]).then(res => {
    console.log(res); // ["成功1", "成功2"]
}).catch(err => {
    console.log(error);
});

// 存在失败的情况时
Promise.all([p3, p1, p2]).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err); // "失败"
});


2. Promise.race

Promise.race([res1, res2, res3])里面哪个结果获得快,就返回哪个结果,不管结果本身是成功还是失败状态。

const res1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success');
    }, 1000);
});

const res2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('failed');
    }, 500);
});

Promise.race([p1, p2]).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err); // 打开的是failed
});


// 还在写
usecallback usememo

虚拟dom
类数组怎么转换数组
可选链怎么用
数组去重
前端自适应方案
hook diff
react diff算法
useReducer
类组件和函数组件最大的区别,什么时候使用
ES6的新特性了解多少,说几个常用的
Redux
Webpack
排序算法
防抖节流有什么不一样
常见的网络攻击
前端优化方案

HTML meta属性干啥用的


● 你的性能优化指标是怎么确定的?平均下来时间减短了多少? 
● 你的性能是如何测试的?有两种主流的性能测试⽅法你是怎么选的? 
● 你是根据哪些指标进⾏针对性优化的? 
● 你的这个优化⽅法在实际操作中碰到过什么问题吗?有没有进⼀步做过测试? 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值