对于浅拷贝来说: 拷贝一个对象,如果属性是基本数据类型,那么它拷贝的就是基本数据类型的值,如果是引用数据类型,那么它拷贝的就是原始对象的数据内存地址。修改拷贝对象会同时修改初始对象 深拷贝 :则是在堆内存中开辟一块新的内存空间,把原始对象拷贝过来,两个对象相互独立,各自地址对应各自的数据 它们之间互不影响。
★区别 浅拷贝是拷贝一层数据,对于引用类型等深层次的数据对象级别只会拷贝它的引用;而深拷贝是拷贝多层,每一级别的数据都会拷贝出来,成为一个独立的拷贝对象,它与初始对象之间互不影响。
基本数据类型与引用数据类型的拷贝 基本数据类型在拷贝的时候 栈内存会开辟新空间 修改互不影响(a=1;b=a;);但它不是真正意义上的深拷贝。 引用数据类型在拷贝的时候 普通的拷贝实质是拷贝了数据引用地址,修改的话也会影响初始对象。
★一些深拷贝的实现方法: 1、递归复制所有层级的属性
判断数据类型 ==>> 定义拷贝匿名变量obj ==>> 条件判断遍历结果是否还为objet/array是则继续深层复制 ==>> 循环遍历拷贝 ==>> 递归执行。 function getType (obj){ //tostring会返回对应不同的标签的构造函数 let toString = Object.prototype.toString; let map = { '[object Boolean]' : 'boolean', '[object Number]' : 'number', '[object String]' : 'string', '[object Function]' : 'function', '[object Array]' : 'array', '[object Date]' : 'date', '[object RegExp]' : 'regExp', '[object Undefined]': 'undefined', '[object Null]' : 'null', '[object Object]' : 'object' }; if(obj instanceof Element) { return 'element'; } return map[toString.call(obj)]; }; function deepClone(data){ var type = getType(data); var obj; if(type === 'array'){ obj = []; } else if(type === 'object'){ obj = {}; } else { //不再具有下一层次 return data; } if(type === 'array'){ for(var i = 0, len = data.length; i < len; i++){ obj.push(deepClone(data[i])); } } else if(type === 'object'){ for(var key in data){ obj[key] = deepClone(data[key]); } } return obj; }只判断拷贝数组和对象的话也可以把程序简化为:
function deepClone(data){ var type = Object.prototype.toString.call(data) var obj; if (type === '[object Array]') { obj = []; } else if (type === '[object Object]'){ obj = {}; } else { //不再具有下一层 return data; }; if (type === '[object Array]') { for (let i = 0; i < data.length; i++) { obj.push(deepClone(data[i])); } } else if (type === '[object Object]'){ for (let key in data) { obj[key] = deepClone(data[key]); } } return obj; };对象深拷贝: 2、使用JSON对象解析实现,即把一个对象转成json字符串在转成json对象,JSON.parse(JSON.stringify(obj)) 缺点是无法拷贝对象中的函数方法。遇到循环引用对象会出错,解析耗费时间多。 3、通过Object.assign()拷贝,缺点是当对现有多级属性时,二级属性后就是浅拷贝 数组深拷贝: 1、concat(arr1, arr2,…) let arr = [1, 2, 3]; let arr1 = [].concat(arr); 2、 slice(idx1, idx2),参数是拷贝起始位置,也可不选。 let arr = [1, 2, 3]; let arr1 =arr.slice; 数组的拷贝方法均对二维数组以上的数组无效,也就是二级属性以上的数据仍是浅拷贝
★深拷贝实际用途 在实际开发中。例如后台返回了一堆数据,你需要对这堆数据做操作,但多人开发情况下,是没办法明确这堆数据 是否有其它功能也需要使用,直接修改可能会造成隐性问题,深拷贝可以更安全安心的去操作数据
作用域是什么: 作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。是执行上下文中可以访问的变量/对象的有限集合。作用域是分层的,内层作用域可以访问外层作用域的变量。而外层不可以访问内层。作用域最大的用处是隔离变量,不同作用域下同名变量不会有冲突。
作用域分类:
全局作用域: 在代码中任何地方都能访问到,反复使用函数作用域: 声明在函数内部,仅在函数内部可以访问块级作用域: 通过 let 和 const 声明,所声明的变量在指定块的作用域外无法被访问它的范围是最接近它的一对花括号内。作用域链:
如果在一个函数中调用自由变量会以定义该函数的作用域为起点通过作用域链向父级作用域一层一层的查找,如果找到全局作用域还没有找到会提示变量未声明的错误。这种多级作用域连续引用形成的关系,就叫作用域链,作用域链只会向上查找,不会向内部作用域查找。 自由变量: 可简单理解为当前作用域没有定义的变量叫自由变量,或者说夸作用域的变量叫自由变量。
作用域链的意义 掌握作用域链有助于尽量将全局变量局部化 ,避免作用域链的层层嵌套,所带来的性能问题和变量污染问题以及因此导致的一些bug。
执行上下文是执行代码的函数环境。每个函数都有其自己的执行上下文。与作用域链的区别是执行上下文是包含其自身作用域的环境。而作用域链是当前上下文中访问的值的引用链。当代码在执行环境中执行时,变量对象的作用域链就被创建了。
一句话概括闭包:闭包就是能够读取其他函数内部变量的函数,或者子函数在外调用,子函数所在的父函数作用域不会被释放。 通俗的讲就是函数a的内部函数b,被函数a外部被引用的时候,就创建了一个闭包。 闭包:中所有自由变量的查找的起始点是以函数被定义地方的作用域为起点,向上级作用域查找,而不是在函数被执行的地方! 闭包的使用场景(用处):
函数封装(最常见的是函数封装的时候)定时器函数传参( 原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。)封装私有变量(当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。)它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。面试QA:(转载自:知乎)
面试官:那你简单写一个闭包吧 应聘者:
function a(){ var i=0; function b(){ alert(++i); } return b; } var c = a(); c();//外部的变量面试官:你这个写法是正确的,那我衍生一下,你回答一下依次会弹出什么:
function a(){ var i=0; function b(){ i++; alert(i); } return b; } var c = a(); c();//? c();//? c();//?应聘者:应该是会依次弹出1,2,3。
面试官:没错,你回答的很对,你知道其中的原理吗?能否解释一下
应聘者:好的,i是函数a中的一个变量,它的值在函数b中被改变,函数b每执行一次,i的值就在原来的基础上累加 1 。因此,函数a中的i变量会一直保存在内存中。
当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。
面试官:非常棒,你说的这是它的一个用处,你能说一下闭包的用处有哪些吗?
应聘者:它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
面试官:那我顺便再出个问题考考你吧,请看题:
var num = new Array(); for(var i=0; i<4; i++){ num[i] = f1(i); } function f1(n){ function f2(){ alert(n); } return f2; } num[2](); num[1](); num[0](); num[3]();应聘者:答案为2,1,0,3(这个时候你了解清楚闭包之后,这个答案应该就是随口而出),解释如下:
//创建数组元素 var num = new Array(); for(var i=0; i<4; i++){ //num[i] = 闭包;//闭包被调用了4次,就会生成4个独立的函数 //每个函数内部有自己可以访问的个性化(差异)的信息 num[i] = f1(i); } function f1(n){ function f2(){ alert(n); } return f2; } num[2](); //2 num[1](); //1 num[0](); //0 num[3](); //3面试官:那你知道闭包的优缺点吗?
应聘者:
优点:
① 减少全局变量;
② 减少传递函数的参数量;
③ 封装;
缺点:
① 使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等
面试官:正好你提到了内存泄漏,说说你的解决方法
应聘者:简单的说就是把那些不需要的变量,但是垃圾回收又收不走的的那些赋值为null,然后让垃圾回收走;
构造函数、实例、原型对象的关系 构造函数、实例、原型对象、Object对象、Object原型对象的关系
原型的概念 每次创建js函数时,js都会为函数对象默认添加一个称为prototype的额外属性,它指向原型对象,可以用来访问原型对象。通俗的说 原型就是一个模板,或者说是一个对象模板
当我们从构造函数(Function 对象)中创建实例的时候,js引擎会再次向该对象的实例添加_ _proto_ _ ,它也可以用于访问Function 对象的同一原型对象。构造函数(Function对象)的原型对象在所有使用该函数创建的实例对象中共享,将方法和属性添加到此原型对象,这些方法和属性将自动用于其构造函数的实例。(在构造函数实例中的__proto__属性中)prototype指向原型对象(显示原型) constructor指向其(构造函数)prototype指向原型对象的函数对象 … 实例的原型属性__ proto__(隐式原型)和函数function的prototype属性指向同一对象(原型对象)。
原型链的定义和继承
定义: 对象的原型,也是个对象。只要对象的原型有值,不为null,那么它就还有原型。所以构成了原型链。 每个被创建的对象都会从原型链上继承原型对象的方法和属性。
详细解释 : 当访问某个对象的属性时如果在内部未找到,那么它就会往它的原型对象上查找,如果还没找到就继续向上查找,直到找到prototype指向null时停止,并返回未声明错误,那么在这个查找的过程层层向上查找的关系就是原型链。
★可以往原型链上添加属性和方法 ,完成手写原型链继承的例子。
拓展:当一个实例调用某个方法时,它先去自己所属类型的原型链上依次去寻找对应方法,最后才去Object的原型对象
promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、 rejected(已失败)它的状态不受外界影响,只由异步操作结果决定。 一旦状态改变就不会再变,任何时候都可以得到这个结果 promise有两种方法 .then当promise实现的时候,状态为fulfilled时被使用。 .catch() 是当promise没有实现的时候,状态为rejected时被使用。
Session 代表服务器与浏览器的一次会话过程,由服务端生成,保存在服务器中,作用就是在服务端存储用户和 服务器会话的一些信息,典型的应用有:判断用户是否登录,购物车功能。 离开网站时销毁。服务器做了负载均衡,下一个操作请求到另一个服务器session会丢失
Cookie 是指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据 (通常经过加密)。Cookie是由服务端生成的,发送给客户端(通常是浏览器)的。 (2)作用 记住密码,下次自动登录。 购物车功能。 记录用户浏览数据,进行商品(广告)推荐。 (3)缺陷 ①Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。 ②由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题。(除非用HTTPS) ③Cookie的大小限制在4KB左右。对于复杂的存储需求来说是不够用的。
Cookie和Session的区别
1、存放位置不同: Cookie保存在客户端,Session保存在服务端。
2 、存取方式的不同: Cookie中保存的是字符串,Session保存的是对象
3、安全性(隐私策略)的不同 : Cookie存储在浏览器中,对客户端是可见的,客户端的一些程序可能会窥探、复制以至修正Cookie中的内容。而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的风险。 假如选用Cookie,比较好的方法是,敏感的信息如账号密码等尽量不要写到Cookie中。最好是像Google、Baidu那样将Cookie信息加密,提交到服务器后再进行解密,保证Cookie中的信息只要本人能读得懂。而假如选择Session就省事多了,反正是放在服务器上,Session里任何隐私都能够有效的保护。
4、有效期上的不同: 只需要设置Cookie的过期时间属性为一个很大很大的数字,Cookie就可以在浏览器保存很长时间。 由于Session依赖于名为JSESSIONID的Cookie,而Cookie JSESSIONID的过期时间默许为–1,只需关闭了浏览器(一次会话结束),该Session就会失效。
5、对服务器造成的压力不同 : Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。 Session是保管在服务器端的,每个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。
6、 跨域支持上的不同 : Cookie支持跨域名访问,例如将domain属性设置为“.baidu.com”,则以“.baidu.com”为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中。而Session则不会支持跨域名访问。Session仅在他所在的域名内有效。
localStorage
webstorage是HTML5新出的标签,是本地存储的解决方案之一,有sessionStorage与localStorage两种。 在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,不同的浏览器中localStorage会有所不同。IE8以上的IE版本才支持localStorage这个属性
存储信息 localStorage.setItem(“a”,3); 获取信息 localStorage.getItem(‘a’); 删除信息 localStorage.removeItem(‘myStorage’); 清除当前域名下所有内容 localStorage.clear();
存入 JSON 对象,需先转换成 JSON 字符串,再写入,在读取时再转换成 JSON 对象 JSON.stringify(data);转换为json字符串写入 JSON.parse(jsonString);转换为json对象读取
webstorage拥有封装好的方法,如setItem, getItem, removeItem,clear等。不像cookie那样需要程序猿手动封装。cookie的作用是与服务器进行交互,作为http规范的一部分存在为webstorage。而webstorage仅仅是为了在本地存储数据而生,它们都保存在客户端浏览器。
cookie,localStorage, sessionStorage三者区别
cookie始终在同源的http请求中携带,即使不需要,cookie在浏览器和服务器中来回传递。而localStorage和sessionStora仅仅在本地存储,不会好服务器通信,也不会自动把数据发送给服务器。 存储大小不同,cookie为4kb左右;localStorage, sessionStorage可以达到5M 数据有效期不同,sessionStorage仅在同源窗口中有效,关闭窗口就消失了,cookie可以设置过期时间,localStorage长期有效 localStorage, sessionStorage有现成的API, cookie需要程序员手动封装
使用最原始的双层循环: 实现方法:假如有一个数组 array,定义一个变量res存储结果 首先最外层循环 array,里面循环 res,如果 array[i] 的值跟 res[j] 的值相等,就跳出循环,如果都不等于,说明元素是唯一的,这时候 j 的值就会等于 res 的长度,根据这个特点进行判断,将值添加进 res。 简单理解:就相当于新拿一个容器,把原来容易里的项一个一个拿进来,在拿进来前先进行一个比较,查看新容器里面的所有内容看是否已经有了,如果有了就放弃(也就是跳出旧容器该项的遍历),没有就添加进来。
原始方法的优点就是兼容性好代码:
var array = [1, 1, '1', '1']; function unique(array) { // res用来存储结果 var res = []; for (var i = 0, arrayLen = array.length; i < arrayLen; i++) { for (var j = 0, resLen = res.length; j < resLen; j++ ) { if (array[i] === res[j]) { break; } } // 如果array[i]是唯一的,那么执行完循环,j等于resLen // 也就是说如果array的这一项没有res里的重复,那么就不会满足if的条件被跳出程序,执行完j++就会加一了 if (j === resLen) { res.push(array[i]) } } return res; } console.log(unique(array)); // [1, "1"]然后可以使用indexOf数组方法 首先循环遍历数组 在循环内使用indexof 检查res 里是否有该循环项,如果没有则添加进res 代码:
var array = [1, 1, '1']; function unique(array) { var res = []; for (var i = 0, len = array.length; i < len; i++) { var current = array[i]; if (res.indexOf(current) === -1) { res.push(current) } } return res; } console.log(unique(array));利用ES6的set数据结构,set的成员值都是唯一的
var unique = (a) => [...new Set(a)]还可以结合使用filter方法,也有很多种方式实现 第一种是结合indexof,
原理是 indexof不指定位置的时候,只返回第一次搜寻到该值时的索引值,若后面出现相同的值依然返回的是第一次搜寻到的索引值
根据这个原理设置过滤条件为 indexOf返回值等于索引值 , (indexOf返回值等于该项索引值就说明是第一次搜索到该值。后面的项虽然indexOf返回值一样,但是索引就不一样了) 就可以完成数组去重
var array = [1, 2, 1, 1, '1']; function unique(array) { var res = array.filter(function(item, index, array){ return array.indexOf(item) === index; }) return res; } console.log(unique(array));第二种是利用Object 键值对利用一个空的 Object 对象,我们把数组的值存成 Object 的 key 值,比如 Object[value1] = true,在判断另一个值的时候,如果 Object[value2]存在的话,就说明该值是重复的。
var array = [{value: 1}, {value: 1}, {value: 2}]; function unique(array) { var obj = {}; return array.filter(function(item, index, array){ console.log(typeof item + JSON.stringify(item)) return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true) }) } console.log(unique(array)); // [{value: 1}, {value: 2}]