JS技术点总结
1 深浅拷贝区别 如何实现
一深浅拷贝区别
浅拷贝:只是拷贝数据的内存地址,而不是在内存中重新创建一个一模一样的对象(数组)
深拷贝:在内存中开辟一个新的存储空间,完完全全的拷贝一整个一模一样的对象(数组)
二 实现浅拷贝方法
一 for···in只循环第一层
// 只复制第一层的浅拷贝
function copy(obj){
var objCopy = {};
for(var key in obj){
objCopy[key] = obj[key];
}
return objCopy;
}
var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var personCopy = copy(person);
二 es6 assign方法
var obj = {
a: 1,
b: 2
}
var obj1 = Object.assign(obj);
obj1.a = 3;
console.log(obj.a) // 3
三 直接赋值 let a=[0,1,2,3,4], b=a; console.log(a===b); a[0]=1; console.log(a,b);
四 实现深拷贝方法
一 采用递归去拷贝所有层级属性 function deepClone(obj){ //这一步判断决定了是决定创建空对象还是空数组 Array是数组创建[] 不是数组就创建{} let objClone = Array.isArray(obj)?[]:{}; if(typeof obj==="object"){ for(key in obj){ //判断key属性是否是自身属性 而非是原型链上的属性 if(obj.hasOwnProperty(key)){ //判断ojb子元素是否为对象,如果是,递归复制 if(obj[key]&&typeof obj[key] ==="object"){ objClone[key] = deepClone(obj[key]); } else{ //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a=[1,2,3,4], b=deepClone(a); a[0]=2; console.log(a,b);
二 通过JSON对象来实现深拷贝 序列化 function deepClone(obj) { var obj2 = JSON.stringify(obj), objClone = JSON.parse(obj2); return objClone; }
2 闭包形成条件 含义 缺点,作用,如何避免
需要满足一下特征才算是形成了闭包
(1)有外层函数嵌套内层函数
(2) 内层函数使用外层函数的局部变量
(3) 内层函数返回外部,并且被全局变量保存
闭包的含义:
闭包概念: 简单理解就是 就是能在函数外部读取函数内部变量的函数
闭包真正的含义 如果一个函数访问了此函数的父级及父级以上的作用域变量 就可以称这个函数是一个闭包
闭包的作用:
闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。
闭包的好处和坏处
好处:
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存
③匿名自执行函数可以减少内存消耗
坏处以及解决方法:
①被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
4 继承常见类型 各自优缺点 实现方法
- 原型链继承:
- 优点:只调用一次父类构造函数,能复用原型链属性
- 缺点:部分不想共享属性也被共享,无法传参。
方法: children.prototype=new parent();
- 构造函数继承:
- 优点:可以传参,同属性可以不被共享。
- 缺点:无法使用原型链上的属性
parent.call(this,name,age,sex); //写在子类里面
- 组合继承=原型链继承+构造函数继承
- 优点:可以传参,同属性可以不被共享,能使用原型链上的属性。
- 缺点:父类构造函数被调用2次,子类原型有冗余属性。
- 原型式继承:(用于对象与对象之间)
- 优点:在对象与对象之间无需给每个对象单独创建自定义函数即可实现对象与对象的继承,无需调用构造函数。
- 缺点:父类属性被完全共享。
ES5 新增了个 Object.create(parentObject) 函数实现原型式继承
- 寄生式继承:
- 优点:基于原型式继承仅仅可以为子类单独提供一些功能(属性),无需调用构造函数。
- 缺点:父类属性被完全共享。
- 组合寄生继承:
- 优点:组合继承+寄生式继承,组合继承缺点在于调用两次父类构造函数,子类原型有冗余属性,寄生式继承的特性规避了这类情况,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。
es6 class继承
Class 可以通过`extends`关键字实现继承
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color; // 正确
}
}
es5 es6两种继承机制不同之处:
1 ES5 的继承,实质是先创造子类的实例对象`this`,然后再将父类的方法添加到`this`上面(`Parent.apply(this)`)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到`this`上面(所以必须先调用`super`方法),然后再用子类的构造函数修改`this`。
super关键字 访问父类的成员方法或变量
1 ES6在继承中强制要求,必须在子类调用super,因为子类的this是由父类得来的。
2 `super`这个关键字,既可以当作函数使用,也可以当作对象使用
3 `super`作为函数调用时,代表父类的构造函数 作为函数时,`super()`只能用在子类的构造函数之中**,用在其他地方就会报错。
4 `super`作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类**。可以在别的方法内调用
5 箭头函数与一般函数区别 它们的this指向 各自应用场景
区别:1 普通函数的this谁调用他就指向谁,箭头函数没有原型,所以没有自己的this,捕获其所在上下文的 this 值,作为自己的 this值,它的this指向上一层的函数作用域
2 箭头函数不绑定arguments
3 箭头函数不能作为构造函数使用
4 箭头函数不能使用call bind apply方法
箭头函数使用场景:
1 适合用在 map、reduce、filter 的回调函数定义中
2 不适合用在需要this指向调用对象的时候
3 不适合用在绑定事件对象,不能用于对象的方法
7 proxy实现数组逆序查询
function createArrayProxy(array) {
if (!Array.isArray(array)) {
throw new TypeError('参数不是数组类型');
}
return new Proxy(array, {
get(target, index) {
// index = +index;
index=parseInt(index) //转化成数值 index在做数组操作的时候会被转成字符串
return target[index < 0 ? target.length + index : index]; //逆序转换为正序 -1的下标就相当于3
},
set(target, index, value) {
// index =+index;
index=parseInt(index)
target[index < 0 ? target.length + index : index] = value;
}
})
}
let arr = ['a','b','c','d'];
let arrProxy = createArrayProxy(arr);
console.log(arrProxy[-1]); // d 触发get
arrProxy[-2] = 'hello'; // 赋值操作触发set
console.log(arrProxy[-2]); // hello
console.log(arr); // ["a", "b", "hello", "d"]
8 typeof valueOf instanceOf 使用以及各自区别
1 typeof 操作符返回一个字符串,表示未经计算的操作数的类型,你可以使用 typeof 操作符来检测变量的数据类型。
console.log(typeof(undefined)); //undefined
console.log( typeof(null)); //object
2 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,你可以用 instanceof判断一个对象是不是某个类型的实例
const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car); //实例对象 instanceof new后面的对象
3 valueOf()通常用于引用数据类型的强制转换,返回指定类型对象的原始值 没有原始值返回本身
三者区别:
1、valueOf()方法返回指定类型对象的原始值。
2、typeof 既可以判断基本数据类型也可以判断部分引用数据类型
3、instanceof只可以判断基本数据类型。
9 谈一谈对原型,原型链,原型对象的理解 原型链解决什么问题 prototype与proto区别
原型链解决的主要是继承问题
一
1 原型链理解:原型链是原型对象创建过程的历史记录,当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构
2 对原型理解
原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。
3 对原型对象理解:
每一个构造函数都有一个prototype属性,这个属性的值是一个对象,这个对象就叫做构造函数的原型对象
二
2 prototype 和 proto 区别是什么?
1)prototype是构造函数的属性
2)__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性
3)实例的__proto__与其构造函数的prototype指向的是同一个对象
3 构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(p.__proto__ === Parent.prototype)
4 补充:
所有构造函数都是Function的实例
所有prototype都是对象 都是object的实例
10 call bind apply三者使用方法 各自区别
相同点:都是用来改变this的指向
不同点:
(1)三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
(2)bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行
11 节流防抖概念 二者区别 应用场景 怎样实现?
概念: 防抖(debounce):触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。 函数节流(throttle):高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率 二者区别: 防抖:在特定时间内,只要重新触发事件,就会重新计算时间,而在这个时间内只会执行最后一次。 也就是说,你只要一直触发这个事件,他就不会执行,停止后才会执行 节流:特定时间内,无论你触发多少次,在这段时间内,仅有一次真正的事件触发 节流应用场景:页面滚动加载,页面刷新 防抖应用场景:表单提交 百度联想查询 : 百度搜nike 会等你输入完nike后 页面跳转 不是你输入完n就立刻给你查询结果
代码实现节流:
主流实现方式 时间戳和定时器
//时间戳 当事件触发时,我们取出当前的时间戳,然后减去之前的时间戳,如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
function throttle(func, wait) { //wait为时间周期
var context, args;
var previous = 0; //previous是之前的时间戳
return function() {
var now = +new Date(); //当前时间距离1970年的总毫秒数 当前的时间戳
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
//定时器 当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行, 然后执行函数,清空定时器,这样就可以设置下个定时器。
function throttle(func, wait) {
var timeout;
var previous = 0;
return function() {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
代码实现防抖
/* fn: 要执行的函数 delay: 延迟执行时间ms args: fn函数的参数(数组) context: fn函数的this指向(默认是函数执行时的上下文环境) */
function debounce(fn, delay, args, context) {
let timer = null;
return function() {
context = context || this;
args = args || arguments;
if(timer != null) {
clearTimeout(timer);
}
timer = setTimeout(function() {
fn.apply(context, args);
}, delay)
}
}
function handle() {
console.log('hahandy');
}
window.onscroll = debounce(handle, 1000);
12 null 和undefined区别
1 类型不同:前者返回的是未定义值后者是对象; 2 转换原始类型方式不同 :前者是不支持转换 后者值为0 3 undefined 是 全局对象 的一个属性。也就是说,它是全局作用域的一个变量。undefined 的最初值就是原始数据类型 undefined 4 null不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象
13 手写bind方法
Function.prototype.myBind = function(context){
// 判断是否是一个函数
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用bind的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments,1)
// 返回一个函数
return function F () {
// 判断是不是new出来的
if(this instanceof F) {
// 如果是new出来的
// 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
return new _this(...args,...arguments)
}else{
// 如果不是new出来的改变this指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}
15 数组去重有几种方法?
1 用includes方法来判断是否包含重复元素。 includes()方法:用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
function removeDuplicate(arr) {
const newArr = []
arr.forEach(item => {
if (!newArr.includes(item)) {
newArr.push(item)
}
})
return newArr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]
2 利用indexOf()方法 新建一个空数组,遍历需要去重的数组,将数组元素存入新数组中,存放前判断数组中是否已经含有当前元素,没有则存入。此方法无法对NaN去重
function removeDuplicate(arr) {
const newArr = []
arr.forEach(item => {
if (newArr.indexOf(item) === -1) {
newArr.push(item)
}
})
return newArr // 返回一个新数组
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN, NaN ]
3 利用es6中set方法 因为每个值在set中只能出现一次
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
4 对象去重 利用对象属性名不可重复的特性
function removeDuplicate(arr) {
const newArr = []
const obj = {}
arr.forEach(item => {
if (!obj[item]) {
newArr.push(item)
obj[item] = true
}
})
return newArr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]
16 JS垃圾回收机制GC
垃圾回收站机制是浏览器JS引擎的内存管理机制,该机制会定期对不再使用的变量进行内存的回收
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行
标记清除法
变量进入执行环境,标记为“进入环境” 变量离开执行环境,标记为“离开环境”,而标记为“离开环境”的变量是可以被回收的 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包)。在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后GC会回收这些变量的内存
17 事件委托及其原理
含义:本来应该注册给子元素的时间,注册给父元素 让父元素来监听事件 原理:事件冒泡 子元素的事件向外冒泡,触发父元素相同的事件,根据事件对象找到真正触发该事件的事件源
18 列举数组 字符串相关方法
数组: push:将元素添加到数组末尾 返回值是数组长度 pop:将数组最后一个元素弹出 返回值是被弹出的元素 unshift:在数组开头插入一个元素,返回值是数组长度 splice:(index,len):删除数组中指定元素 返回被删除的项目 reverse:翻转数组 contact:连接两个数组 返回一个新数组 不改变原数组 join:将数组中元素使用定义的连接符拼接成字符串 返回拼接后的结果 注意:join方法的参数不传或者传入undefined会默认用逗号分隔 字符串: substr:(index,len):截取字符串 返回被截取的值 slice:(start,end) 不包括end 返回被截取后的值 replace(pre,nex) 将前面的替换为后面的 indexOf:返回某个指定的字符串值在字符串中首次出现的位置返回其下标 如果没有找到匹配的字符串则返回 -1
19 new操作符具体干了什么呢
1 创建一个新对象 2 将this指向空对象 3 给刚创建的对象添加成员属性 4 隐式返回this
20 ==和===什么区别
===是严格意义上的相等,会比较两边的数据类型和值大小 数据类型不同返回false 数据类型相同,但值大小不同,返回false ==是非严格意义上的相等, 两边类型相同,比较大小 两边类型不同,根据下方表格,再进一步进行比较。 Null == Undefined ->true String == Number ->先将String转为Number,在比较大小 Boolean == Number ->现将Boolean转为Number,在进行比较 Object == String,Number,Symbol -> Object 转化为原始类型
21 什么是作用域 什么是作用域链
规定变量和函数的可使用范围称作作用域 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。
22 变量提升和函数提升
函数提升优先级大于变量提升 题目如下:
console.log(person)
console.log(fun)
var person = 'jack'
console.log(person)
function fun () {
console.log(person)
var person = 'tom'
console.log(person)
}
fun()
console.log(person)
变量提升后变成:
var fun=function(){
var person;
console.log(person); //undefined
person='tom';
console.log(person); //tom
}
var person;
console.log(person); //undefined
console.log(fun); // 函数体
person='jack';
console.log(person); //jack
fun();
console.log(person); //jack
23 DOM面试题 将虚拟节点具象成实际标签
var element = {
tagName: 'div',
id: 'header',
className: 'title fw400 c666 fz28',
contentText: '我是一个header标签',
children: [
{
tagName: 'h1',
id: '',
className: 'logo',
contentText: '我是logo',
children: []
},
{
tagName: 'ul',
id: '',
className: 'nav',
contentText: '',
children: [
{
tagName: 'li',
id: '',
className: 'list',
contentText: '导航1',
children: []
},
{
tagName: 'li',
id: '',
className: 'list',
contentText: '导航2',
children: []
},
]
}
]
}
function openElement(element, oB) {
var str1 = Object.keys(element);//获取关键字(key) 返回一个由一个给定对象 的自身可枚举属性组成的数组
//console.log(str1) ['tagName', 'id', 'className', 'contentText', 'children'x5]
var oDiv = document.createElement(element[str1[0]]); //document.createElement ()是在对象中创建一个对象 创建大div
oDiv.id = element['id'];
oDiv.className = element['className'];
oDiv.innerText = element['contentText'];
oB.appendChild(oDiv); // 把div标签添加到ob里面(ob取决于自己传的实参是啥)
// 这一步是遍历div里面的数据(h1,ul,li) 渲染数据 把数据都渲染到div标签
// if (Array.isArray(element['children']) && element['children'].length > 0) {//判断children是不是数组,且长度大于0
for (var i = 0; i < element['children'].length; i++) {
openElement(element['children'][i], oDiv);//如果children是数组 且长度大于0,则将children中的数组继续执行函数,直至结束
}
// }
}
openElement(element, document.body);
24 Ajax请求GET和POST的区别
1.使用 Get请求时,参数在 URL 中显示,而使用 Post 方式,则不会显示出来 2.使用 Get请求发送数据量小, Post 请求发送数据量大
25 cookie localstorage sessionstorage 三者区别
一 生命周期不同 cookie:可设置失效时间,没有设置的话,默认是关闭浏览器后失效 localStorage:除非被手动清除,否则将会永久保存。 sessionStorage: 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。 二 存储数据大小不同 cookie:4KB左右 localStorage和sessionStorage:可以保存5MB的信息。
26 实现跨域有哪些方式
1 jsonp实现跨域
原理:通过script标签资源引入 浏览器同源策略无法限制,只能用于get请求
实现: 1 前端创建一个回调函数,用来接收服务端返回的数据。
2 前端动态插入 script 标签执行请求
3 后端将数据和 js 回调函数名拼接为函数调用的字符串并返回给客户端。
以callback(data)的形式
4 前端接收到 script 标签响应并自动执行回调函数
2 cors后端来实现跨域
CORS
服务端设置
//设置允许跨域的域名,*代表允许任意域名跨域
res.setHeader("Access-Control-Allow-Origin", "*");
//我容许跟我不同源的页面像我发起请求
//允许的header类型
res.setHeader('Access-Control-Allow-Headers', 'x-requested-with,Authorization,token, content-type');
//跨域允许的请求方式
res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,PATCH");
//可选,用来指定本次预检请求的有效期,单位为秒。在此期间,不用发出另一条预检请求。
res.setHeader('Access-Control-Max-Age', 1728000);//预请求缓存20天
简单请求 || 非简单请求
简单请求 Content-Type:只限于三个值`application/x-www-form-urlencoded`、`multipart/form-data`、`text/plain`
urlencoded : key=value&key=value
multipart/form-data 表单对象
text/plain 纯文本
非简单请求都会发起预验证 OPTIONS
只有OPTIONS请求通过才会发送客户端实际请求