前端面试题(一)

1.介绍一下box-sizing的属性?

1.box-sizing 规定两个并排的带边框的框,

2.box-sizing的属性值:content-box/border-box/inherit

content-box:(默认的盒子,设置padding和border会撑开盒子,使盒子比原本的宽高更大,padding=>内边距,margin=>外边距)宽度和高度分别应用到元素的内容框,在宽度和高度之外绘制元素的内边距和边框 .

border-box:(包含了padding和border,设置两者不会撑开盒子,盒子大小不会改变)为元素设定的宽度和高度决定了元素的边框盒.就是说,为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。

inherit:继承父元素的 box-sizing.

2.浮动元素引起的问题和解决方法?

(1)浮动元素引起的问题?

1.由于浮动元素已脱离文档流,所以父元素无法被撑开,影响与父级元素同级的元素。
2.与浮动元素同级的非浮动元素(内联元素)会跟随其后,也是由于浮动元素脱离文档流,不占据文档流中的位置。
3.如果该浮动元素不是同级第一个浮动的元素,则它之前的元素也应该浮动,否则容易影响页面的结构显示。

(2)清除浮动的方式:

1.使用带 clear 属性的空元素

在浮动元素后使用一个空元素如<div class="clear"></div>,并在 CSS 中赋

予.clear{clear:both;}属性即可清理浮动。亦可使用<br class="clear" />或<hr class="clear" />来进行清理。

2.使用 CSS 的 overflow 属性

给浮动元素的容器(父元素)添加 overflow:hidden;或 overflow:auto;可以清除浮动,另外在 IE6 中还 需要触发 hasLayout ,例如为父元素设置容器宽高或设置 zoom:1。在添加 overflow 属性后,浮动元素又回到了容器层,把容器高度撑起,达到了清理浮动的效果。

3.给浮动的元素的容器添加浮动 

给浮动元素的容器也添加上浮动属性即可清除内部浮动,但是这样会使其整体浮动,影响布局,不推荐使用。

4.使用邻接元素处理

什么都不做,给浮动元素后面的元素添加 clear 属性。

5.使用 CSS 的:after 伪元素

结合:after 伪元素(注意这不是伪类,而是伪元素,代表一个元素之后最近的元素)和 IEhack ,可以完美兼容当前主流的各大浏览器,这里的 IEhack 指的是触发 hasLayout。给浮动元素的容器添加一个 clearfix 的 class,然后给这个 class 添加一个:after 伪元素现元素末尾添加一个看不见的块元素(Block element)清理浮动。

即:给父元素添加clearfix样式:

.clearfix:after {
    content: ".";
    display: block;
    height: 0;
    clear: both;
    visibility: hidden;
}

.clearfix {
    display: inline-block;
}

3.什么是BFC?

        BFC 也就是常说的块格式化上下文,这是一个独立的渲染区域,规定了内部如何布局, 并且这个区域的子元素不会影响到外面的元素,其中比较重要的布局规则有内部 box 垂 直放置,计算 BFC的高度的时候,浮动元素也参与计算,触发 BFC 的规则有根元素, 浮动元素,position 为absolute 或 fixed 的元素,display 为 inline-block,table-cell,table-caption,flex,inline-flex,overflow 不为 visible 的元素.

4.哪些操作会造成内存泄漏,怎么解决?

内存泄漏:指一块被分配的内存既不能使用,又不能回收,无法被释放.,直到浏览器进程结束。

1.意外的全局变量引起的内存泄露

function leak() {
    leak = "xxx";//leak成为一个全局变量,不会被回收
}

2.闭包引起的内存泄露

function bindEvent() {
    var obj = document.createElement("XXX");
    obj.onclick = function () {
        //Even if it's a empty function
    }
}

闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包。

解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

//将事件处理函数定义在外部
function onclickHandler() {
    //do something
}
function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = onclickHandler;
}

//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent() {
    var obj = document.createElement("XXX");
    obj.οnclick = function () {
        //Even if it's a empty function
    }
    obj = null;
}

3.没有清理的DOM元素引用

DOM引用造成内存泄漏:Dom 的操作,会把Dom 的引用保存在一个数组或者 Map 中,标记已经移除,DOM对象还在内存中

var elements = {
    button: document.getElementById("button"),
    image: document.getElementById("image"),
    text: document.getElementById("text")
};
function doStuff() {
    image.src = "http://some.url/image";
    button.click();
    console.log(text.innerHTML)
}
function removeButton() {
    document.body.removeChild(document.getElementById('button'))
}

4.被遗忘的定时器或者回调

var someResouce = getData();
setInterval(function () {
    var node = document.getElementById('Node');
    if (node) {
        node.innerHTML = JSON.stringify(someResouce)
    }
}, 1000)

这样的代码很常见, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。

5.子元素存在引起的内存泄露

 黄色是指直接被 js变量所引用,在内存里,红色是指间接被 js变量所引用,如上图,refB 被 refA 间接引用,导致即使 refB 变量被清空,也是不会被回收的子元素 refB 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除。

6.IE7/8引用计数使用循环引用产生的问题

function fn(){
  var a={};
  var b={};
  a.pro=b;
  b.pro=a;
}
fn();

fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄漏。在IE7与IE8上,内存直线上升。

7.怎样避免内存泄露?

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象  原则:不用了的东西要及时归还。

5.浏览器的本地存储方案有哪些以及使用场景是什么?

1.sessionStorage存储

sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问,并且当会话结束后数据也随之销毁。sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。

2.localStorage存储

localStorage:主要是用来作为本地存储使用的,解决了cookie存储空间不足的问题(cookie的每条cookie存储不超过4k),localStorage中一般浏览器支持的是5M大小,这个在不同浏览器会有所不同。localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

3.globalStorage

Firefox支持globalStorage,能读写所有域的存储数据的localStorage。但是Firefox 13.0后被废弃了。

例子:

globalStorage[‘developer.mozilla.org’]

解析: 在developer.mozilla.org下面所有的子域都可以通过这个存储对象来进行读和写。

4.Cookie

1.创建cookie

        document.cookie="name=小明"

2.添加cookie有效期,时间是UTC格式

        document.cookie = "name=小明; expires=Sun, 31 Dec 2017 12:00:00 UTC";

3.设置path 参数告诉浏览器 cookie 属于什么路径。默认情况下,cookie 属于当前页。

        document.cookie = "name=小明; expires=Sun, 31 Dec 2017 12:00:00 UTC; path=/";

4.读取cookie

        var cookie=doucument.cookie

        console.log("cookie:"+cookie)

打印出来的结果:

会在一条字符串中返回所有 cookie,比如:cookie1=value; cookie2=value; cookie3=value;

5.删除cookie:利用expires参数,将cookie的有效期设置为过去的时间

    document.cookie="name=小明;expires=Sun,29 Dec 2017 12:00:00 UTC ;path/"

注意:定义 cookie 路径以确保删除正确的 cookie。

6.创建cookie函数

    function setCookie(cname, cvalue, exdays) {

            var d = new Date();

            d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));//设置cookie的有效期

            var expires = "expires="+d.toUTCString();

            document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";

            }

7.获取cookie函数

   function getCookie(cname) {

              var name = cname + "=";

              var ca = document.cookie.split(';');

              for(var i = 0; i < ca.length; i++) {

              var c = ca[i];

              while (c.charAt(0) == ' ') {

                 c = c.substring(1);

              }

              if (c.indexOf(name)  == 0) {

                 return c.substring(name.length, c.length);

              }

            }

            return "";

    }

8.检查cookie函数

   function checkCookie() {

    var user = getCookie("username");

    if (user != "") {

        alert("Welcome again " + user);

    } else {

        user = prompt("Please enter your name:", "");

        if (user != "" && user != null) {

            setCookie("username", user, 365);

        }

    }

console.log("时间:"+new Date().getTime())

}

6.Js去重的方法有哪些?

arr: [
        { id: 1, name: 'a' },
        { id: 1, name: 'f' },
        { id: 2, name: 'b' },
        { id: 3, name: 'c' },
        { id: 3, name: 'g' },
        { id: 4, name: 'd' },
        { id: 5, name: 'e' },
      ],

1.双重for循环

deweight() {
    for (let i = 0; i < this.arr.length - 1; i++) {
        for (let j = i + 1; j < this.arr.length; j++) {
            if (this.arr[i].id == this.arr[j].id) {
                this.arr.splice(j, 1)
                //注意这里要j--,splice删除了一个元素,下标要减一,否则循环会漏掉一个元素
                j--
            }
        }
    }
    console.log(this.arr)
},

2.reduce()

deweight() {
    var obj = {}
    this.arr = this.arr.reduce(function (data, item) {
        console.log(obj[item.id])
        obj[item.id] ? '' : obj[item.id] = true && data.push(item)
        return data;
    }, [])
    console.log(this.arr)
}

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

reduce() 可以作为一个高阶函数,用于函数的 compose。

注意: reduce() 对于空数组是不会执行回调函数的。

3.Map()

has方法可以判断Map对象中是否存在指定元素,有则返回true,否则返回false
set方法可以向Map对象添加新元素 map.set(key, value)
values方法可以返回Map对象值的遍历器对象

deweight() {
    let map = new Map()
    for (let i of this.arr) {
        if (!map.has(i.id)) {
            map.set(i.id, i)
        }
    }
    this.arr = [...map.values()]
    console.log(this.arr)
}

4.for 循环

deweight() {
    let newArr = [];
    let obj = {};
    for (var i = 0; i < this.arr.length; i++) {
        if (!obj[this.arr[i].id]) {
            newArr.push(this.arr[i]);
            obj[this.arr[i].id] = true;
        }
    }
    console.log(newArr);
}

5.every()

every() 方法用于检测数组所有元素是否都符合指定条件,所有元素都满足条件,则返回 true,有一个元素不满足,则返回 false ,且剩余的元素不会再进行检测.

deweight() {
    let newArr = []
    this.arr.forEach(function (a) {
        let istrue = newArr.every(function (b) {
            return a.id != b.id
        })
        istrue ? newArr.push(a) : ''
    })
    console.log(newArr)
}

这几种方法都是用 对象的某个key做为唯一标识,相同则删除!

7.Js对象的浅克隆和深克隆的区别是什么以及实现方式是什么?

浅克隆:浅复制只复制一层对象的属性.

深克隆本质上是创造一个完全一样的对象,但是两个对象的引用地址完全不同,也就是不同与浅克隆,一个对象的改变,另外一个对象并不会发生改变。

浅克隆是指:只克隆数组/对象的第一层级内容(开辟新的堆内存),而第二层级及以上层级的内容则直接引用(使用原来第二层级及以上层级的堆内存)。如果对克隆后对象的二级或以上层级进行修改,那么克隆前对象的二级或以上层级也会跟着被修改。

1.对象的浅克隆方案一:基于循环实现

let obj1 = {
	python: "python",
	java: "java"
	fontend: {
		javascript: "javascript",
		html: "html"
	}
}
let obj2 = {};//创建一个新对象(开辟新的堆内存)
//把obj1中私有的key和Symbol类型的key存在数组中
let keys = [
	...Object.keys(obj1),//展开运算符
	...Object.getOwnPropertySymbols(obj1)
]
keys.forEach(key => {
	obj2[key] = obj1[key];
});
console.log(obj1 === obj2); //false
console.log(obj1.fontend === obj2.fontend);// true

2.对象的浅克隆方案二: 展开运算符

let obj1 = {
	python: "python",
	java: "java"
	fontend: {
		javascript: "javascript",
		html: "html"
	}
}
let obj2 = {
	...obj1
};
console.log(obj1 === obj2); //false
console.log(obj1.fontend === obj2.fontend);// true

3.对象的浅克隆方案三:基于Object.assign()函数

Object.assign([obj1], …[obj2]):该方法接收多个参数,执行后会将obj2中的键值对合并到obj1中,并将obj1的堆内存地址返回。注意:这里返回的不是新的对象,而是把第一个参数作为返回值返回。所以我们可以利用这个特点进行对象的浅克隆

let obj1 = {
	python: "python",
	java: "java"
	fontend: {
		javascript: "javascript",
		html: "html"
	}
}
//因为assign方法默认会把第一个参数返回,所以这里传一个空对象进去,执行asign方法将obj1合并后,再将合并后的对象返回,就达到了克隆目的
let obj2 = Object.assign({}, obj1);

console.log(obj1 === obj2); //false
console.log(obj1.fontend === obj2.fontend);// true

4.数组浅克隆方案一:基于forEach或map

let arr = [12,3,[4,50]];
let arr2 = [];
arr.forEach((item, index) => {
	arr2[index] = item;
});

arr2 = arr.map(item => item);

5.数组浅克隆方案二:基于展开运算符或Object.asing(),与对象相同

let arr = [12,3,[4,50]];
let arr2 = [
	...arr
];

arr2 = Object.assign([], arr);

6.数据浅克隆方案三:基于数组的slice方法,不传参数默认从数组第一项开始截取到数组的最后一项

let arr = [12,3,[4,50]];
let arr2 = arr.slice();

7.数组和对象的浅克隆 - 通用方案

//定义一个用于检测数据类型的通用方法
function toType(value){
let obj = {};
['Number','String','Boolean','Null','Undefined','Symbol','BigInt','Function','GeneratorFunction','Date','RegExp','Object','Error'].forEach(item => {
	obj["[object "+item+"]"] = item.toLowerCase()
});
 return obj[Object.prototype.toString.call(value)];
}
//定义一个获取对象/数组所有私有属性(包括Symbol类型)的方法
function getOwnProperties(obj){
//数据类型检测
if(obj == null) return [];
//获取所有私有属性包括Symbol类型
let keys = [
	...Object.keys(obj),
	...Object.getOwnPropertySymbols(obj)
]
return keys;
}

function shallowClone(obj){
	//如果是基本数据类型值,则传啥就返回啥
	let type = toType(obj);
	if(/^(number|string|boolean|null|undefined|symbol|bigint)$/.test(type)) return obj;
	if(type === 'function') {
		//返回一个不同函数,但最后执行效果跟原始函数一致
		return function proxy(){
			obj();
		}
	}
	//if(type === 'regexp') return new RegExp(obj);
	//if(type === 'date') return new Date(obj);
	if(/^(regexp|date)$/.test(type)) return new obj.constructor(obj);
	if(type === 'error') return new Error(obj.message);
	
	let keys = getOwnProperties(obj);
	let clone = {};
	Array.isArray(obj) ? clone = [] : null;
	keys.forEach(item => {
		clone[item] = obj[item];
	});
	return clone;
}

深克隆:用递归复制了对象的所有层级.

浅克隆是指在克隆对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行克隆。就是将栈中的值复制一份给新的变量,因为浅复制只会将对象的各个属性进行依次复制,并不会进行递归复制,而 JavaScript 存储对象都是存地址的,所以浅复制会导致两个对象指向都是同一个地址,一个发生改变另外一个也发生改变。

前面说浅克隆是只克隆数组或对象的第一层级,二级及以上层级是直接引用;那么深克隆则是克隆数组或对象的每个层级,不管一个对象或数组有多少层级,那么当我们进行深克隆时每一个层级都会开辟一块新的堆内存地址。对于克隆后对象/数组的任何层级做任何修改都不会影响到克隆前的对象/数组,因为它们都是独立的不同的堆内存。
 

1.数组和对象的深克隆:基于JSON.stringfiy、JSON.parse实现深克隆

原理:先将数组或对象基于JSON.stringify转换为字符串,然后再基于JSON.parse把字符串转换为对象,此时对象中对应每个层级都会开辟一块全新的堆内存来存储
缺点:因为JSON.stringify变为字符串,很多类型是不支持的

        正则/Math数学函数会被处理为空对象
        具备函数/Symbol/undefined属性值的属性会被直接删除掉
        BigInt无法处理,会报错
        日期对象转换后变为字符串

let obj1 = {
	python: "python",
	java: "java"
	fontend: {
		javascript: "javascript",
		html: "html"
	}
}
let obj2 = JSON.parse(JSON.stringify(obj1));

2. 数组/对象深克隆:

function toType(value){
let obj = {};
['Number','String','Boolean','Null','Undefined','Symbol','BigInt','Function','GeneratorFunction','Date','RegExp','Object',"Error"].forEach(item => {
	obj["[object "+item+"]"] = item.toLowerCase()
});
 return obj[Object.prototype.toString.call(value)];
}
//定义一个获取对象/数组所有私有属性(包括Symbol类型)的方法
function getOwnProperties(obj){
//数据类型检测
if(obj == null) return [];
//获取所有私有属性包括Symbol类型
let keys = [
	...Object.keys(obj),
	...Object.getOwnPropertySymbols(obj)
]
return keys;
}

function deepClone(obj, cache = new Set()){	
	/*
	if(/^(number|string|boolean|null|undefined|symbol|bigint)$/.test(type)) return obj;
	if(type === 'function') {
		//返回一个不同函数,但最后执行效果跟原始函数一致
		return function proxy(){
			obj();
		}
	}
	//if(type === 'regexp') return new RegExp(obj);
	//if(type === 'date') return new Date(obj);
	if(/^(regexp|date)$/.test(type)) return new obj.constructor(obj);
	if(type === 'error') return new Error(obj.message);
	*/
	//如果是基本数据类型值,则传啥就返回啥
	let type = toType(obj);
	//如果不是数组或对象则直接按浅克隆处理
	if(!/^(array|object)$/.test(type)) return shallowClone(obj);
	
	let keys = getOwnProperties(obj);
	let clone = {};
	Array.isArray(obj) ? clone = [] : null;
	if(cache.has(obj)) return obj;
	cache.add(obj);
	keys.forEach(item => {
		//if(/^(array|object)$/.test(toType(obj[item]))){
		//	clone[item] = deepClone(obj[item]);
		//}
		clone[item] = deepClone(obj[item], cache);
	});
	return clone;
}

3.深克隆(函数、日期格式、正则有点问题)

/*JSON.stringify(obj)先把对象变成字符串
	但:函数、日期格式、正则在JSON.stringify都会出现问题
	eg:  JSON.stringify({ a: function(){}, x:1000 }) 
	===> "{ "x": 1000 }"  
	函数这部分就消失了
	eg:  JSON.stringify({ a: new Date() }) 
	===> "{ "a": "2019-12-27T13:35:45.081Z" }"  
	会转成具体时间的字符串*/
/*JSON.parse再把字符串重新变成一个对象
	会把所有的值重新开辟新的空间*/
let obj2 = JSON.parse(JSON.stringify(obj));    

项目中很多时候我们用这种方式就够了,因为很少会遇到对象里有函数、正则、日期格式这样的。

4.深克隆(层级递归)

由浅克隆的问题,可以知道,我们只要把每一层都遍历到,并赋值给新的对象的每一项的每一层级即可。那么就会想到——递归

function deepClone(obj){
    // ==>过滤特殊情况,做一个数据类型检测,只有对象才走下面的代码
    if(obj === null) return null;
    if(typeof obj!=="object") return obj;
    // 检测正则,Date,typeof是检测不出来的, 要用instanceof
    if(obj instanceof RegExp){
        return new RegExp(obj);
    }
    if(obj instanceof Date){
        return new Date(obj);
    }
    //==>不直接创建空对象的目的: 克隆的结果和之前保持相同的所属类
    //解析:
    //obj的constructor指向它的所有类
    // 一个实例的constructor指向它的所有类
    // 如果传过来的是一个实例,接下来克隆出来的那个结果,还是当前这个类的实例
    // 这么做,既能克隆普通对象,又能克隆某一个类的实例
    // 这样克隆出来的结果,依然还是这个类的实例
    
    let newObj = new obj.constructor;
    
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
           //newObj[key] =  obj[key]; 这样的话就还只是浅克隆			//所以我们需要递归
           newObj[key] = deepClone(obj[key]);
        }
    }
    
    return newObj;
    
    let obj2 = deepClone(obj);
    console.log(obj,obj2);
    console.log(obj === obj2);  //false
    console.log(obj.c === obj2.c);  //false
}

8.谈谈你对闭包的理解。你有哪些性能优化的方法?

简单来说,闭包就是在函数里面声明函数,实际开发中主要应用于封装变量,保护变量不受外界污染,也相当于是在函数作用域里面再声明一个内部作用域,这样执行结果拿到的变量都是不同的,拿的就不是全局变量。

特性:函数内部嵌套函数

缺点:闭包容易消耗内存

注意:子函数可以访问父函数中所有的局部变量,,但是父函数不能访问子函数的变量

创建:创建闭包最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用域链,将函数内部的变量和方法传递到外部。

闭包解决方法:在函数结束之前,把局部变量删除。

9.为什么vue组件中的data必须是一个函数? 

        类比引用数据类型。Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;

        javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。

例:

const MyComponent = function() {};
MyComponent.prototype.data = {
    a: 1,
    b: 2,
}
const component1 = new MyComponent();
const component2 = new MyComponent();

component1.data.a === component2.data.a; // true;
component1.data.b = 5;
component2.data.b // 5

如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改;

两个实例应该有自己各自的域才对,需要通过下面的方法来进行处理。

const MyComponent = function() {
    this.data = this.data();
};
MyComponent.prototype.data = function() {
    return {
        a: 1,
        b: 2,
    }
};

        这样的话一个实例的data属性都是独立的,不会相互影响了.
        所以,你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关。其实vue不应该把这个方法名取为data(),应该叫setData或其他更容易理解的方法名。

10.什么是virtual DOM ?

        所谓的 virtual dom,也就是虚拟节点。它通过 JS 的 Object 对象模拟 DOM 中的节点,然后再通过特定的 render 方法将其渲染成真实的 DOM 节点。

        其次我们还得知道一点,那就是 virtual dom 做的一件事情到底是啥。我们知道的对于页面的重新渲染一般的做法是通过操作 dom,重置 innerHTML 去完成这样一件事情。而 virtual dom 则是通过 JS 层面的计算,返回一个 patch 对象,即补丁对象,在通过特定的操作解析 patch 对象,完成页面的重新渲染。(改变需要改变的部分,其他地方不动!)

11.key属性的作用什么?

        当 Vue.js 用v-for正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。(*如果不用key,则会都改变许多顺序,用key则会改变需要改变的地方!)

        总体来说,当使用列表渲染时,永远添加key属性,这样可以提高列表渲染的效率,提高了页面的性能。

使用key属性强制替换元素:

        key属性还有另外一种使用方法,即强制替换元素,从而可以触发组件的生命周期钩子或者触发过渡。因为当key改变时,Vue认为一个新的元素产生了,从而会新插入一个元素来替换掉原有的元素。key属性被用在组件上时,当key改变时会引起新组件的创建和原有组件的删除,此时组件的生命周期钩子就会被触发。

12.vue2和vue3的区别是什么?

1. vue2和vue3双向数据绑定原理发生了改变。

vue2 的双向数据绑定是利用ES5 的一个 API Object.definePropert()对数据进行劫持 结合 发布订阅模式的方式来实现的。vue3 中使用了 es6 的 ProxyAPI 对数据代理。

相比于vue2.x,使用proxy的优势如下:

        defineProperty只能监听某个属性,不能对全对象监听。

        可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)。

        可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化 

2. 默认进行懒观察(lazy observation)。

        在 2.x 版本里,不管数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力。3.x 版本,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 3.x 的观察者更高效。

3. 更精准的变更通知。

        比例来说:2.x 版本中,使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行;3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。

4. 3.0 新加入了 TypeScript 以及 PWA 的支持

5.vue2和vue3组件发送改变

6.vue3.0的设计目标

  • 更小
  • 更快
  • 加强TypeScript支持
  • 加强API设计一致性
  • 提高自身可维护性
  • 开放更多底层功能

13.封装后的高阶组件?

import React from 'react';
import Left from './left.js';
import { Right } from './right.js';

function addsname(params) {
    class Height extends React.Component {
        constructor() {
            super()
            this.state = { nameinfo: null, scour: [] }
        }
        addName(event) {
            this.setState({ nameinfo: event.target.value })
        }
        getScour(n, s) {
            this.state.scour.push({ names: n, scour: s })
            this.setState(this.state.scour)
        }
        render() {
            return (
                <div>
                    <Right scour={this.getScour.bind(this)}></Right>
                    <Left out={this.state.scour}></Left>
                </div>
            )
        }
    }
    return Height;
}
var Newname = addsname('苹果');
export default Newname;

14.react优化?
1.通过PureComponent进行优化
        两者的区别在于React.Component并未实现shouldComponentUpdate(),而React.PureComponent中以浅层对比 prop和 state 的方式来实现了该函数。如果赋予React组件相同的props和 state,render()函数会渲染相同的内容,那么在某些情况下使用React. PureComponent可提高性能如果内容相同,就不会再从新渲染。
2.memo
        新版本发布的memo跟 PureComponent很相似,都是来帮助我们控制何时渲染组件的。正常我们的组件是只要你的props和 state 改变了,就会重新渲染一次组件,但是PureComponent、memo就是来帮助我们在有需要的时候渲染组件的。这对性能是一个大大提升。但是在使用PureComponent只能在es6中的class组件使用,但memo却可以在function Component中使用。从这点也可以看出,react中会增加对Hook 的重视。

15.前端性能优化?
前端性能优化主要是为了提高页面的加载速度,优化用户的访问体验。我认为可以从这些方面来进行优化。
第一个方面是页面的内容方面
(1)通过文件合并、css雪碧图、使用base64等方式来减少HTTP请求数,避免过多的请求造成等待的情况。
(2)通过DNS 缓存等机制来减少DNS的查询次数。

(3)通过设置缓存策略,对常用不变的资源进行缓存。
(4)使用延迟加载的方式,来减少页面首屏加载时需要请求的资源。延迟加载的资源当用户需要访问时,再去请求加载。
(5)通过用户行为,对某些资源使用预加载的方式,来提高用户需要访问资源时的响应速度。

第二个方面是服务器方面
(1)使用CDN服务,来提高用户对于资源请求时的响应速度。
(2)服务器端启用Gzip、Deflate 等方式对于传输的资源进行压缩,减小文件的体积。
(3)尽可能减小cookie 的大小,并且通过将静态资源分配到其他域名下,来避免对静态资源请求时携带不必要的cookie
第三个方面是CSS和 JavaScript方面
(1)把样式表放在页面的head标签中,减少页面的首次渲染的时间。

(2)避免使用@import标签。
(3)尽量把 js 脚本放在页面底部或者使用defer 或 async属性,避免脚本的加载和执行阻塞页面的渲染。
(4)通过对JavaScript和CSS的文件进行压缩,来减小文件的体积。

16.组件优化?
用shouldcomponent这个生命周期来对state做一个浅层的比较。也可以用purecomponent 做一个浅层的比较因为他集成了shouldcomponent的功能。

17.proxy 代理(拦截器)?
1. proxy 代理一般用于数据过滤,是一个拦截器。

2.当我的变量显示或改变的时候,会调用proxy的两个方法,get和set。

3.get 方法默认就会调用,set方法当变量改变的时候去调用。

18.webpack常用loader?
1. babel-loader :转换 ES6、ES7、JSX等语法
2. css-loader:用于加载.css文件,并且转为common.js模块对象,可以使用import进行导入

3. style-loader:将样式通过<style></style>标签插入<head></head>之间
4. less-loader :支持less文件转换成css
5. ts-loader :将TS转换为js
6. file-loader
        他有两个功能:
        (1)让文件或图片可以像模块一样导入通过import .

        (2)file-loader将文件或图片改名并复制到打包目录.

7. thread-loader多进程打包 js和css
8. image-webpack-loader对图片进行压缩
9. url-loader拷贝图片,对于指定大小的图片进行base64转码

10.raw-loader来实现静态资源内联.

19.扁平化?

1.es5扁平化:

function isArray(obj) {//判断是否是数组
    return Object.prototype.toString.call(obj) == '[object Array]'
}
var arr = [1, [1], 2, 3, [2, 3, 4, [null, 2, 3, 4[1]]]];
function flatten(arr) {
    var arr = arr || [],
        resArr = [],
        len = arr.length
    for (let i = 0; i < len; i++) {
        if (isArray(arr[i])) {
            resArr = resArr.concat(flatten(arr[i]))//数组中可能还有数组
            // flatten(resArr)//递归失败的原因
        } else {
            resArr.push(arr[i])
        }
    }
    return resArr
}

2.es6扁平化:

const flattenArray = arr => {
    const falttened = [].concat(...arr);
    return falttened.some(v => Array.isArray(v)) ? flattenArray(falttened) : falttened;
}

const arr = [1, [1], 2, 3, [2, 3, 4, [null, 2, 3, 4[1]]]];
console.log(flattenArray(arr));

function isArray(obj) {//判断是否是数组
    return Object.prototype.toString.call(obj) == '[object Array]'
}

3.迭代删除数组里某几项内容:

let data = [
    { name: "lily", age: 18 },
    { name: "tom", age: 19 },
    { name: "luv", age: 17 },
    { name: "tsdf", age: 21 },
    { name: "tsdf", age: 20 },
];
data.forEach((value, index, arr) => {//arr代表数组的本身
    if (value.age == 18) {
        arr.splice(index, 1)
    }
})

4.先排序后去重

/*
思路:
1.先排序
2.遍历数组每个元素,让前一个元素与后一个元素相比较,若相等,删除前一个。
注意:每次删除元素会使数组长度减一,每次循环时会少对比一个,所以删除后i--
*/
var arr = [1, 2, 5, 46, 6, 8, 7, 4, 5, 12, 45, 1, 2]
// 排序(sort是让数组从小到大进行排序)
arr.sort(function (x, y) {
    return x - y;
});
// 去重
for (var i = 0; i < arr.length; i++) {
    if (arr[i] == arr[i + 1]) {
        arr.splice(i, 1);//数组已经删除了重复选项
        i--;
    }
}
console.log(arr);

5.sum函数:

// 要求实现一个sum函数,其调用方式和返回结果如下例:
// sum(1,2)(3).valueOf(),//6
// sum(4)(3,2,1)(5,6).valueOf();//21

// //实现sum函数

// //sum(1,2)(3).valueOf();

// //6


function sum(...arg){
  var arr=[...arg];
  var add=0;
 arr.forEach(value=>add+=value);
  var tmp=function(...y){
     [...y].forEach(v=>add+=v);
     return tmp;
  }
  tmp.valueOf=function(){
    return add;
  }
  return tmp;
}
console.log(sum(1,2)(2,2)(1).valueOf())

20.mock的作用,平时怎么搭建?

mock模拟的是后端调来的数据(假数据),在开发中如果后端进度跟不上,可以使用mock代替后端数据,当后端数据写完后,再改地址调接口取数据,用法 export js文件,import接收,如果webpack没有支持json文件需要设置json文件,再进行接收.

21.什么是hoc,举例说明?

高阶组件就是hoc,高阶组件就是一个获取一个组件并返回一个组件的函数,react-redux 的conect就是一个高阶组件。

22.写出redux里dispatch和action的具体作用?

        dispatch:用于action分发,可以用中间件对dispatch进行改造,比如当action传入dispatch会立即触发reducer有时候我们不希望它立即触发,而是等待异步操作完成后再触发,这时候用redux-thunk对dispatch进行改造,以前只能传入一个对象,改造完成后可以传入一个函数,在这个函数里我们手动dispatch一个action对象这个过程是可控的,就实现了异步;

        action(处理类型不一样处理方法不同)根据action的type类型不同会返回不同的reducer。

23.写出mwm模式和mvc模式区别?

mvvm是双向数据流,mvc是单数据。两者都由m(模型)、v(视图)

不同的是:mvvm内由vm(模型视图)负责业务逻辑,mvc由c(控制器)负责业务逻辑

24.react的生命周期?(实际项目组用到的react生命周期钩子有哪些?都是在什么时候触发的?)?

实际项目中用的react生命周期方法,在初始化阶段,es5中用到getDefaultProps和getInitialState,es6中通过类的继承从component中继承,挂载阶段实际项目中用到的是render和componentDidMount,render在state和props更新时被调用,子组件的render会在父组件的render执行时被执行,更新阶段一般不覆盖更新阶段的钩子函数,所以在开发时不会用到,卸载阶段一般不用,所以卸载阶段的钩子函数也不用,修改后的生命周期方法:

componentWillMount()=>组件挂载前  和componentWillUpdate()=>组件更新后

 1.React16新的生命周期弃用了componentWillMount、componentWillReceivePorps, componentWillUpdate
⒉新增了getDerivedStateFromProps、getSnapshotBeforeUpdate来代替弃用的三个钩子函数(componentWillMount.componentWillReceivePorps,componentwillupdate)
3.React16并没有删除这三个钩子函数,但是不能和新增的两个钩子函数(getDerivedStateFromProps,getSnapshotBeforeUpdate)混用。注意:React17将会删除componentWillMount、componentWillReceivePorps,componentWillUpdate
4.新增了对错误处理的钩了函数(componentDidCatch)

25.为什么虚拟dom会提高性能?

虚拟DOM相当于在js和真实DOM之间加了一个缓存,利用dom,diff算法避免了没有必要的dom操作从而提高性能。

26.React组件间通信?

1.父组件通过props/this.props给子组件传值

      子组件通过父组件传递过来的回调函数的参数给父组件传值

        App--todo->Item  Item--changHasCompleted(todo)->App

         说明:适合于不超过三层

2.redux 集中式数据管理--通用的数据流解决方案

3.context

4.事件订阅

27.react传值的方法?

1.通过props属性实现组件间传值。

2.通过prop-types的context实现跨级组件传值。

3.使用事件订阅实现组件间跨级传值。

4.使用redux实现组件跨级通信.

28.生命周期方法?

从组件创建,到组件的更新,直到组件销毁过程中,按照固定的顺序自动调用的方法。

  1.getDefaultProps  es6改为 类名.state  初始化属性

  2.getInitialState  es6改为 this.state  初始化状态

  3.componentWillMount  渲染组件前调用,在客户端也在服务端

  4.render  渲染组件

  5.componentDidMount  在第一次渲染组件后调用,只在客户端

  6.componentWillReceiveProps  准备接收属性(在组件接收到一个新的prop时被调用)

  7.shouldComponentUpdate  是否更新组建,返回一个布尔值,在组件接收到新的props或state时被调用

  8.componentWillUpdate  准备更新组件(在组件收到新的props或者state但还没有render时被调用)

  9.componentDidUpdate  更新组建后(在初始化时不会被调用)

  10.componentWillUnmount  准备卸载组件(在组件从dom中移除的时候立刻被调用)

React15版本的生命周期:

React 16版本的生命周期:

 

废弃了:

componentWillMount、componentWillReceiveProps、componentWillUpdate。

新增了:

1.static getDerivedStateFromProps(props, state)

组件每次被rerender的时候,包括在组件构建之后(虚拟dom之后,实际dom挂载之前),每次获取新的props或state之后;每次接收新的props之后都会返回一个对象作为新的state,返回null则说明不需要更新state;配合componentDidUpdate,可以覆盖componentWillReceiveProps的所有用法。

2.getSnapshotBeforeUpdate(prevProps, prevState)

触发时间: update发生的时候,在render之后,在组件dom渲染之前;返回一个值,作为componentDidUpdate的第三个参数;配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法。

  3. Error Handling(错误处理)

componentDidCatch(error,info)  任何一处的javascript报错会触发。

 29.React特点?

  1.较高的性能:虚拟DOM、diff算法

  2.虚拟DOM--js对象

  3.组件化--实现代码高度复用

  4.JSX语法:用xhtml写DOM对象

  5.单向数据流 父--->props->子

30.react脚手架?

1.从webpack开始,纯手工独立搭建的webpack脚手架

2.facebook官方提供create-react-app  0配置脚手架

3.roadhog 0配置脚手架

31.react组件传值?

1.父组件向子组件传值 (通过props来传值)

2.子组件给父组件传值 (回调函数)

3.兄弟组件传值(子组件传给父组件,由父组件再传给另外一个子组件)

32.react组件分类?

1.静态组件--函数

2.类组件 --class

      --ES5 React.createClass({});    

      --ES6 extends React.Component。

33.redux?redux的原理了不了解?(redux的实现过程--分几块?)?

        edux主要是数据中心化和跨级通信。

        数据中心化实现是通过创建全局store,在store中创建state树,存储数据。

        跨级通信是通过provider组件包裹入口组件,然后在入口组件及入口组件的后代组件中通过高阶组件connect连接store,获取数据。

34.redux的原理及缺点?

    创建一个全局store,store中存放着多条state状态,哪里需要用state哪个组件就connect链接到全局store中,更改store需要发送dispatch(action)到reducer中,reducer更改state后,页面重新渲染 (忘了就去看一下举火库那张图)

    缺点: 需要维护大量constant,action,reducer

35.redux-saga 中间件和工作流程?

中间件:

中间件  或 中间件函数  redux middleware

中间:因为middleware位于 action 和reducer之间

Redux第三方中间件:redux-thunk

中间件执行原理:拦截action,然后转发action

工作流程:

redux-saga:用于异步处理ajax的中间件

     --解决回调地狱的问题

     --实现异步操作

优势:可读性强 可维护强。

36.redux  redux-saga开发中存在的问题?

1.文件非常多-难以维护

   constants

   actions

   reducers

   sagas

2.代码冗余

  综上所述,开发和维护困难

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
很抱歉,我无法提供具体的2024年前端面试题一百道,因为面试题的内容和形式可能会因公司、职位和行业而异。不过,我可以提供一些通用的前端面试题,以帮助您准备面试。 1. 请简述前端开发的主要技术栈包括哪些? 2. 请描述一下什么是HTML、CSS和JavaScript? 3. 请解释一下什么是响应式设计?如何在前端开发中实现响应式设计? 4. 请简述一下什么是前端框架,并列举几个常用的前端框架。 5. 请解释一下什么是Vue.js,并简述其核心概念和用法。 6. 请解释一下什么是React.js,并简述其核心概念和用法。 7. 请简述一下什么是Webpack,并解释其作用和用法。 8. 请解释一下什么是ES6,并列举一些ES6的新特性。 9. 请简述一下什么是前端性能优化,并列举一些优化技巧。 10. 请解释一下什么是HTTP/2,并简述其优点和缺点。 除了以上问题,您还可以准备一些更具体的问题,例如: 1. 请解释一下如何使用CSS选择器选择元素? 2. 请解释一下如何使用JavaScript操作DOM? 3. 请描述一下如何使用Vue.js实现一个简单的计数器组件。 4. 请解释一下如何使用React.js实现一个简单的表单组件。 5. 请描述一下如何使用Webpack进行代码拆分和优化。 6. 请解释一下什么是跨域问题,并简述如何解决跨域问题。 7. 请描述一下如何使用JavaScript进行异步编程,例如使用Promise和async/await。 8. 请解释一下什么是前端安全,并列举一些常见的安全问题及其解决方法。 希望以上信息对您有所帮助,祝面试成功!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值