刷面经
day1( 商汤科技):
var a = function() {console.log(11)};
var b = function() {console.log(11)};
console.log( a==b ); //false
console.log( {}=={} ); //false
console.log( []==[] ); //false
堆、栈:
栈:自动分配内存空间,系统自动释放,一级缓存,被调用时处于存储空间,调用完释放 堆:动态内存分配,大小不定不会自动释放,存放二级缓存,生命周期由垃圾回收算法决定。
js中基本数据类型和引用数据类型
基本类型:存放栈内存,数据大小确定,内存空间可分配 Undefined Null Boolean Number String 按值存放,可以直接访问 引用类型: 存放堆内存,每个空间大小不一样,特定分配 访问引用数据类型(对象、数组、函数),首先从栈中获得该对象的地址指针,再从堆内存中获得所需数据。
var a = function(){}; var b = function(){}; a==b 为false
变量a实际保存的是指向堆内存对象的一个指针,b保存的另一个对象的指针,虽然两个对象值一样,但是他们是两个独立对象,占两份内存空间
如果
var a = {}; var b = a
它们都指向堆内存中同一个对象;所以 a==b 为 true。
传值与传址
var a = [1,2,3,4,5];
var b = a;
var c = a[0];
console.log(b); // [1,2,3,4,5]
console.log(c); // 1
b[4] = 6;
c = 7;
console.log(a[4]); //6
console.log(a[0]); //1
b变a变,c变a不变,这就是传值与传址的区别。a是数组属于引用类型,a传给b的是栈中地址不是堆内对象,c仅是a堆内存的一个数值保存在栈中。 b修改时会根据地址取a堆内存修改,c直接在栈中修改。
浅拷贝
对于基本数据类型的拷贝,并没有深浅拷贝的区别 如果属性是对象或数组时,这时我们传递的只是一个地址,子对象访问该属性的时候会根据地址回溯到父对象指向的堆内存中,二者关联,两者属性值会指向同一内存
Object.assign
Object.assign
是Es6中Object的一个方法,该方法用于JS对象合并,可以实现浅拷贝
target
指的是目标对象,sources
指的是源对象 Object.assign(target,...sources)
- 如果拷贝 基本数据类型 拷贝的就是基本数据类型的值--老死不相往来
- 如果拷贝引用类型 拷贝的是内存地址--关系依然存在
扩展运算符
扩展运算符可以在构造字面量对象的时候,进行属性的拷贝
let obj1 = {a:1 , b:{c:1}}
let obj2 = {...obj1}
数组浅拷贝
Array.prototype.slice
slice()方法是JS数组方法,可以从已有数组中返回选定元素,不改变原数组,只会返回一个浅拷贝了原数组的元素的一个新数组,两个数组不受影响
//两个参数都不写,就可以实现一个数组的浅拷贝:
const animals = [.....]
console.log(animals.slice())
let arr = [1, 2, 3, 4];
let newArr = arr.slice()
console.log(arr.slice() === arr); // false
arr.push(5)
console.log(arr); // [1, 2, 3, 4, 5]
console.log(newArr); // [1, 2, 3, 4]
Array.prototype.concat
合并两个或多个数组,此方法不会更改原始数组,而是返回一个新数组如果省略了所有参数,则会返回调用此方法的现存数组的一个浅拷贝(新数组)
let arr = [1,2,3,4]
let newArr = arr.concat([5])
console.log(arr.concat() === arr); // false
手写实现浅拷贝
function shallowCopy(params) {
if (!params || typeof params !== 'object') {
return params
}
let newObject = Array.isArray(params) ? [] : {}
for (let key in params) {
if (params.hasOwnProperty(key)) {
newObject[key] = params[key]
}
}
return newObject
}
let params = { a: 1, b: { c: 1 } }
let newObj = shallowCopy(params)
console.log(params)
console.log(newObj)
params.a = 2222
params.b.c = 666
// 拷贝对象中---基本类型老死不相往来,引用类型藕断丝连
console.log(params)
console.log(newObj)
总结: 看到所有浅拷贝只能拷贝一层对象。存在对象嵌套,浅拷贝就无能为力,深拷贝就是为了解决多层对象嵌套问题,实现彻底拷贝。
深拷贝
JSON.parse(JSON.stringify(obj))
原理:利用JSON.stringify 将 JavaScript 对象转化为JSON字符串,并将对象里面的内容转换成字符串,再使用JSON.parse来反序列化,将字符串生成一个新的 JavaScript对象.
简单粗暴,但也存在一些问题,如果拷贝对象中有 function,undefined,symbol 当使用过JSON.stringify()进行处理的时候,都会消失。
- 无法拷贝不可枚举的属性;
- 无法拷贝对象的原型链;
- 无法拷贝对象的循环应用,即对象成环 (
obj[key] = obj
)。
const originObj = { name: 'test',
age: undefined,
func: function () {
console.log('Hello World'); },
key: Symbol('一个独一无二的key') } const cloneObj = JSON.parse(JSON.stringify(originObj)); console.log(cloneObj); // 只剩下 {name: "test"}
函数库lodash
该函数库也有提供_.cloneDeep
用来做深拷贝,可以直接引入并使用:
粗略手写实现深拷贝
使用for in
来遍历传入参数的属性值
如果值是基本类型就直接复制
如果是引用类型就进行递归调用该函数
function deepClone(source) {
if (source instanceof Object === false) return source
let target = Array.isArray(source) ? [] : {}
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = deepClone(source[i])
}
else {
target[i] = source[i]
}
}
}
return target;
}
const obj = {
info: { c: { d: 1 } },
age: undefined,
func: function () {
console.log('hello world')
},
key: Symbol('key')
}
const resultA = deepClone(obj)
console.log(111111, obj);
console.log(222222, resultA);
let resultB = [1, [2, 3], [4, [5]]]
let resultC = deepClone(resultB)
resultB[1][1] = 7
console.log(333333, resultB);
console.log(444444, resultC);
- 存在
环引用
问题(存在循环引用,拷贝会直接爆栈)
总结
- 赋值运算符
=
实现的是浅拷贝(引用类型),拷贝的是对象的引用地址; JavaScript
中数组或者对象自带的拷贝方法都是首层浅拷贝;JSON.stringify
实现的是深拷贝,但是对目标对象的部分拷贝结果存在问题; 官方loadsh库已经做得非常完美了,用起来不香?实际工作中建议直接使用;
day2
typeof null // "Object"
-- Null 表示一个空对象引用。
NaN --表示不是数字,是JS的特殊值,出现原因是将字符串解析成数字时出现错误,虽然表示不是数字,但仍是数值类型(number) 需要注意的是NaN不等于任何值,包括它本身。 console.log(NaN===NaN); //结果为false
八股
说说cookie localstorage sessionstorage
Cookie(4KB)(保存在客户端)
--由于HTTP是无状态协议,不能保存每一次请求的状态,所以给客户端增加Cookie来保存客户端状态。主要作用于用户识别和状态管理(网页里常见的记住密码)--传输管理Cookie时要确保 Cookie 的 安全性,不被窃取,Cookie 往往用来 保存用户的登录状态
HTML5 提供了两种在客户端存储数据的新方法 : localstorage -sessionStorage 挂载到window对象下。 localStorage--生命周期是永久性的,localStorage存储的数据,即使关闭浏览器也不会数据消失,除非主动删去,想设置失效时间,需自行封装
sessionStorage -- 生命周期实在浏览器相关,关闭浏览器或者页面,sessionStorage就会失效,页面刷新不会消除数据,只有在当前页面打开的链接才能访问 seesionStorage的数据,使用window.open打开页面和改变localtion.href方式都可以获 取到sessionStorage内部的数据;
说说同源策略,跨域的解决办法
跨域 --请求如果触发了同源策略就会出现跨域问题。 A网页设置的 Cookie,B网页不能打开 同源就是--协议 ,域名,端口相同。
--跨域是由于浏览器的同源策略所导致的,浏览器的同源策略是指只有“协议、域名、端口”相同我们才能进行通信,当有一个不满足的时候,我们就无法进行跨域。 目的:同源政策保证用户信息安全,防止网站恶意窃取数据 限制范围 如果非同源,有三种行为受限。
- Cookie ,LocalStorage,IndexDB无法读取
- Dom 无法获得
- AJAX 请求不能发送
解决方法
- JSONP、CORS、WEBPACK配置
- JSONP,需要前后端配合--利用了 script 标签不受浏览器的同源策略影响的性质来做的,当然还需要前后端的配合
- 客户端事先准备一个接收数据的全局函数
- 客户端解析道外联的Script标签,发出请求
- 服务端收到请求,返回函数的调用
- 客户端收到数据,执行回调。
- CORS--实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
const express = require('express')
const cors = require('cors')
const server = express()
server.use(cors())
server.get('/list', (req, res) => {
res.send({
code: 1, msg: '请求成功'
})
})
server.post('/info', (req, res) => { res.send({ code: 1, msg: '请求成功' }) })
server.listen('8081', () => console.log('接口服务器开启成功'))
判断变量类型的方法有哪些
ECMAScript 标准定义了 8 种数据类型:
Boolean
Null
undefined
Number
String
Biglnt
Symbol
除了Object 以外的类型(基本类型)都是不可以变的(值本身无法被改变)
例如: js字符串不可变,js对字符串操作返回一个新的字符串,原始字符串没有改变,称这些类型值为原始值
BigInt 可以安全存储和操作大整数,常常通过末尾附加n或调用构造函数来构建
const a = BigInt('43243242424242424242342432')
// 43243242424242424242342432n
const b = 43243242424242424242342432n
Symbol
Es6新增的一种基本数据类型,可以通过内置函数Symbol()创建,这个函数会生成一个匿名,全局唯一的值。
Symbol函数栈不能用new 命令,是原始数据类型,不是对象。Symbol最大的用处就是:避免对象的键被覆盖。
typeof
对于基本数据类型来说,除了null返回的是object,其他都可返回正确的类型
-
null被认为是一个空对象,因此返回了object
-
null转为二进制则表示全为0,如果前三个均为0,js就会把它当作是对象,这是js早期遗留下来的bug
-
适用于判断(除null)基础类型,
-
判断引用类型,除了function 全返回object类型
instanceof
判断 变量的原型链上是否有构造函数的prototype属性 (两个对象是否属于原型链的关系) 不一定能获取对象的具体类型
[] instanceof Array
为true。--[].proto 的原型是指向 Array.prototype,说明两个对象属于同一条原型链,返回trueperson(函数) instanceof Object
也为true 参数使用了null。也就是说将null设置成了新创建对象的原型,自然就不会有原型链上的属性
constructor
每一个实例对象都可通过constructor 来访问它的构造函数 undefined和null是无效的对象,因此是没有constructor属性的,这两个值不能用这种方法判断.
'5'.__proto__.constructor === String // true [5].__proto__.constructor === Array // true
toString
Object.prototype.toString方法 返回对象的类型字符串,因此可以用来判断一个值的类型。实例对象有可能会自定义toString方法使用时加上call。所有数据类型都可以使用此方法检测,非常准确。
console.log(Object.prototype.toString.call('5'))
console.log(Object.prototype.toString.call(5))
console.log(Object.prototype.toString.call(555555555n))
console.log(Object.prototype.toString.call(new Function()))
console.log(Object.prototype.toString.call(undefined))
console.log(Object.prototype.toString.call(null))
总结:typeof 适合基本数据类型和function类型,无法判断null 和object
instanceof 适合自定义对象,也可以检测原生对象,注意Object.create(null) 对象的问题
constructor基本可以判断所有类型,除了null 和 undefined ,但是 constructor 容易被修改
tostring能判断所有类型,可将其封装为全能的DataType()判断所有数据类型