一、深拷贝和浅拷贝的区别?实现一个深拷贝。他们经常使用在什么场景之中?(常考题)
1、区别
浅拷贝:创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:其中一个对象改变了这个地址,就不会影响到另一个对象
2、浅拷贝的使用场景
Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
let a = {
name: "chenzixu",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let b = Object.assign({}, a);
展开语法 Spread
let a = {
name: "chenzixu",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let b = {...a};
Array.prototype.slice()
slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
let a = [0, "1", [2, 3]];
let b = a.slice();
console.log(b);
// [0, "1", [2, 3]]
a[1] = "99";
a[2][0] = 4;
console.log(a);
// [0, "99", [4, 3]]
console.log(b);
// [0, "1", [4, 3]]
可以看出,改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝,相应的还有concat等,在工作中面对复杂数组结构要额外注意
3、深拷贝使用方法
- JSON.parse(JSON.stringify())
- 手写递归函数
- 函数库lodash的 _.cloneDeep、 JQuery 的 $.extend都实现了深拷贝
4、实现深拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
二、instanceof 和 typeof 的区别
- typeof 运算符,返回一个值的数据类型。有几种结果:原始类型、函数、undefined、object
- instanceof 是判断变量是否为某个对象的实例。返回值为true或false
参考:www.jianshu.com/p/1216b1f42…
三、for in 和for of的区别
简单说,for in是遍历键名,for of是遍历键值。
- 推荐在循环对象属性的时候,使用
for...in,在遍历数组的时候的时候使用for...of。
for...in循环出的是key,for...of循环出的是value
注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足
for...of不能循环普通的对象,需要通过和Object.keys()搭配使用
参考:segmentfault.com/q/101000000…
四、es6 对象新增的方法有哪些 哪些用的比较多
1、Object.is()
其行为与===基本一致,用来比较两个值是否严格相等。不同之处:一是+0不等于-0,二是NaN等于自身
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
2、Object.assign()
用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)上
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
3、Object.getOwnPropertyDescriptors()
返回指定对象所有自身属性(非继承属性)的描述对象
4、_proto__属性
// es5 的写法
const obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
// es6 的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };
5、Object.getPrototypeOf()
用于读取一个对象的原型对象
6、Object.setPrototypeOf()
作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身
7、Object.keys()
返回一个成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
8、Object.values()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
9、Object.entries()
方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
10、Object.fromEntries()
是Object.entries()的逆操作,用于将一个键值对数组转为对象
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
该方法的一个用处是配合URLSearchParams对象,将查询字符串转为对象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
五、Object.assign 实现原理
- 判断原生 Object 是否支持该函数,如果不存在的话创建一个函数 assign,并使用 Object.defineProperty 将该函数绑定到 Object 上。
- 判断参数是否正确(目标对象不能为空,我们可以直接设置{}传递进去,但必须设置值)。
- 使用 Object() 转成对象,并保存为 to,最后返回这个对象 to。
- 使用 for..in 循环遍历出所有可枚举的自有属性。并复制给新的目标对象(使用 hasOwnProperty 获取自有属性,即非原型链上的属性)。
六、webpack 打包速度怎么优化(常考题)
webpack 性能优化
-
开发环境性能优化
-
生产环境性能优化
开发环境性能优化
- 优化打包构建速度 HMR
- 优化代码调试 source-map
一般用eval-source-map hidden-source-map 只隐藏源代码,会提示构建购代码错误信息
生产环境性能优化
- 优化打包构建速度
1. oneOf
2. babel 缓存
3. 多进程打包
4. externals
5. dll
- 优化代码运行的性能
1. 缓存(hash,chunkhash,contenthash)
2. tree shaking
3. code split
4. 懒加载和预加载
5. pwa
参考:mp.weixin.qq.com/s/BIY1ugDhL…
七、箭头函数和普通函数的区别?
1、this的指向问题
箭头函数的this指向的是父级作用域的this,是通过查找作用域链来确定 this 的值也就是说,看的是上下文的this,指向的是定义它的对象,而不是使用时所在的对象;普通函数指向的是它的直接调用者。
2、箭头函数不能使用arguments对象,该对象在函数体内不存在,如果要用就用rest参数替代。
function f1(arr) {
console.log(arguments);
}
f1([1,2,3]); // [1,2,3]
let f2 = (arr) => {
console.log(arguments);
}
f2([1,3,9]); //Uncaught ReferenceError: arguments is not defined
let f3 = (...arr) => {
console.log(arr);
}
f3([1,4,5]); // [1,4,5]
3、箭头函数无法作为构造函数来使用
不能被当作构造函数来使用,通过new命令来作为构造函数会报错,这里没有构建原型的说法,不存在prototype这个属性,也不能通过super访问原型的属性,而且new target也是不能用的。
4、箭头函数不能使用yield命令, 因此不能使用Generator函数
八、跨域有几种方法,具体怎么实现的?(常考题)
1、JSONP
callback
2、CORS
Access-Control-Allow-Origin
3、降域(document.domain)
这种方式只能用于二级域名相同的情况下,比如a.test.com和 b.test.com 适用于该方式。 只需要给页面添加 document.domain = 'test.com' 表示二级域名都相同就可以实现跨域。
4、postMessage
// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com') {
console.log('验证通过')
}
})
5、window.name
6、Web Sockets
九、事件委托怎么实现的?
function delegate(element, eventType, selector, fn) {
element.addEventListener(eventType, e => {
let el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && fn.call(el, e, el)
},true)
return element
}
十、判断有序数组B是否为有序数组A的子集
var isSubset = function isSubset(a, b) {
if (b.length == 0) return true;
var p1 = 0,
p2 = 0;
while (p1 < a.length) {
if (b[p2] == a[p1]) {
p2++;
if (p2 == b.length) return true;
}
p1++;
}
return false;
};