很久没有写博客了…因为这段时间都没有做新的项目,一直在复习前端的基础知识,大部分的(面试)知识点都归类为体系放入了印象笔记里。但因为还是有些繁杂,所以刚好再整理一次发到博客上来。 注:部分图片来自网络 知识点是仅个人根据面经以及自己的面试经历整理,仅供参考
基本数据类型存放在栈中,数据大小确定,直接按值存放,所以可以直接按值访问
前面说到变量是储存在栈中的,但是对于引用数据类型来说,存储在栈中的仅仅是一个指针,这个指针指向堆内存(真正的对象变量)。
如上图,从obj1复制一个对象到obj2,仅仅是复制了一个指针而已,指向的还是同一个东西。这就是js中,引用数据类型的浅拷贝。
对于基本数据类型,复制变量相当于把值重新赋给新变量;但对于引用数据类型来说,仅仅拷贝指针,即仅仅实现了浅拷贝。
浅拷贝 对于仅仅是复制了引用(地址),即复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响。
在开发工程过程中,肯定需要避免这种情况,更多的是希望能够完全复制出一个新对象。即深拷贝,即在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响。
综上: 深浅拷贝的主要区别: 复制的是引用(地址)还是复制的是实例。
Array的slice 和 concat 方法 和 jQuery 中的 extend 复制方法。都只能复制第一层的值,对于第一层是深拷贝,但是到第二层之后就只复制引用,所以不是真正的深拷贝。
真正的深拷贝
JSON 对象的 parse 和 stringifyJSON 对象中的 stringify 可以把一个 js 对象序列化为一个 JSON 字符串,parse 可以把 JSON 字符串反序列化为一个 js 对象,这两个方法实现的是深拷贝。(两个概念,序列化与反序列化)在任何值上调用 Object 原生的toString() 方法,都会返回一个[object NativeConstructorName]格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。
console.log(Object.prototype.toString.call(und));//[object Undefined] console.log(Object.prototype.toString.call(nul));//[object Null] console.log(Object.prototype.toString.call(arr));//[object Array] console.log(Object.prototype.toString.call(obj));//[object Object] 不能检测非原生构造函数的构造函数名。无论构造函数是什么,只输出[object object] function test(){} var obj = new test(); console.log(Object.prototype.toStirng.call(obj)); //[object object]利用js提供的函数
parseInt()parseFloat() ,Number(),Boolean()toString()进行数据转换,例如:
num.toString(2)能直接将num转换为2进制数格式的字符串parseInt方法可以将其它进制转换为十进制,只需要给该方法传入需要转换的字符串和该字符串的进制表示两个参数即可。利用操作符进行转换都可以看做隐式转换。
例如:
运算中,+号,数字隐式转换成字符串。其余的运算符号是字符串隐式转换成数字。类数组是有数组行为的对象
对于一个普通的对象来说,如果它:
所有元素key值均为正整数有相应的length属性甚至具有数组的方法那么虽然该对象并不是由Array构造函数所创建的,它依然呈现出数组的行为,就是类数组对象
//类数组示例 var a = {'1':'gg','2':'love','4':'meimei',length:5}; Array.prototype.join.call(a,'+');//'+gg+love++meimei' //非类数组示例 var c = {'1':2}; //没有length属性就不是类数组常见的类数组有:
arguments对象set对象、map对象DOM方法的返回结果类数组转为数组
Array.from()var a = {'0':1,'1':2,'2':3,length:3}; var arr = Array.prototype.slice.call(a); //arr=[1,2,3]数组转为类数组
使用apply , call虽然变量a是一个基本数据类型,但他却能跟对象一样,调用相应的方法。
这是因为在基本类型中,number, string, boolean这三个基本类型都有自己的包装对象,并且随时等待召唤。
当对基本数据类型进行属性操作时,JavaScript后台会为其建立一个包装对象,调用包装对象下的方法。调用完毕后就被销毁。
var str = 'hello'; var s2 = str.charAt(0); //在执行到这一句的时候 后台会自动完成以下动作 alert(s2);//h alert(str);//hello //注意这是一瞬间的动作 实际上我们没有改变字符串本身的值。就是做了下面的动作.这也是为什么每个字符串具有的方法并没有改变字符串本身的原因。实际上后台会进行以下操作
var str = new String('hello'); // 找到对应的包装对象类型,然后通过包装对象创建出一个和基本类型值相同的对象 var s2 = str.chaAt(0); // 然后这个对象就可以调用包装对象下的方法 str = null; //之后这个临时创建的对象就被销毁了, str =null;console.log(0.1+0.2===0.3) // false console.log(0.1+0.2) // 0.30000000000000004
解决
设置一个误差范围值(机器精度)在ES6中,Number.EPSILON值正等于2^-52,即通常使用的机器精度 function numbersequal(a,b) { return Math.abs(a-b)<Number.EPSILON; } var a=0.1+0.2, b=0.3; console.log(numbersequal(a,b)); //trueNaN 即 Not a Number ,不是一个数字。 它是 Number 对象上的一个静态属性。
1. 强制类型转换失败 直接使用 parseInt,parseFloat 或 Number 将一个非数字的值转化为数字时,表达式返回 NaN 。
'abc' - 3 // NaN parseInt('abc') // NaN parseFloat('abc') // NaN Number('abc') // NaN Number('123abc'); // NaN parseInt('123abc'); // 123*特别的,Number 转换的是整个值,而不是部分值;parseInt 和 parseFloat 只转化第一个无效字符之前的字符串。另外,一元加操作符也可以实现与 Number 相同的作用。 *
+ '12abc'; // NaN + '123'; // 123 + '123.78'; // 123.78 + 'abc'; // NaN2. 运算符运算失败
在做运算(减乘除等)时,JS会将运算符两边或一边的变量先转换为数字在进行计算。如果转换失败,则表达式将返回NaN
100 - '2a' ; // NaN '100' / '20a'; // NaN '20a' * 5 ; // NaN undefined - 1; // NaN, Number(undefined) == NaN [] * 20 ; // 0, Number([]) == 0 null - 5; // -5, Number(null) == 0特殊的,+运算符通常会将数字转换为字符串,因此不会返回NaN 5 + 4 + '6' = '96';
isNaN方法检查一个值是否能被 Number() 成功转换 。 如果能转换成功,就返回 false,否则返回 true 。
isNaN(NaN) // true 不能转换 isNaN('123') // false 能转换 isNaN('abc') // true 不能转换 isNaN('123ab') // true 不能转换因为NaN 就是除了数字的任意值,但绝不是确切的某一个值,所以它是一个范围,而不是一个确定的值。所以NaN !== NaN
变量提升即:将变量声明提升到它所在作用域的最开始的部分。 例如:
console.log(global); // undefined var global = 'global'; console.log(global); // global function fn () { console.log(a); // undefined var a = 'aaa'; console.log(a); // aaa } fn();由于js的变量提升,实际上上面的代码是按照以下来执行的:
var global; // 变量提升,全局作用域范围内,此时只是声明,并没有赋值 console.log(global); // undefined global = 'global'; // 此时才赋值 console.log(global); // 打印出global function fn () { var a; // 变量提升,函数作用域范围内 console.log(a); a = 'aaa'; console.log(a); } fn();js创建函数的方式有两种:
函数声明(存在函数提升,会提升到该块级作用域最前面) function fun(){}函数表达式 var fun = function(){}JavaScript中有 6 个值为“假”,这六个值是
falsenullundefined0(包括+0, -0)’ ’ (空字符串)NaN这些假值可以被强制类型转换成false。
事件冒泡与捕获
对于addEventListner来说,第三个参数: - true - 事件句柄在捕获阶段执行 - false- 默认。事件句柄在冒泡阶段执行而onclick只能使用事件冒泡是否能重复
onclick一次只能对一个元素绑定一个事件处理程序,如果绑定多个,会被覆盖掉。而addEventListner可以给一个事件注册多个listeneraddEventListner对任何DOM元素都有效,而onclick只对HTML有效
从上往下
具体流程: window(窗口对象) -> document -> html -> body -> 。。。
用js获取 : document.documentElement
用js获取body document.body
从当前事件往外冒泡
事件流描述的是从页面中接收事件的顺序。
事件发生时会在元素节点与根节点之间按照特定的顺序传播,路径所经过的所有节点都会收到该事件,这个传播过程即DOM事件流。
DOM标准采用捕获+冒泡。两种事件流都会触发DOM的所有对象,从document对象开始,也在document对象结束。
DOM标准规定事件流包括三个阶段:
事件捕获阶段:实际目标(<div>)在捕获阶段不会接收事件。也就是在捕获阶段,事件从document到<html>再到<body>就停止了。图中1~3
处于目标阶段:事件在<div>上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。
冒泡阶段:事件又传播回文档。
点击事件:
onclick:单击ondbclick:双击焦点事件:
onblur: 失去焦点onfocus:获得焦点加载事件:
onload:页面或图片完成加载鼠标事件:
onmousedown: 按下鼠标onmouseupL:松开鼠标onmousemove:移动鼠标onmouseover:鼠标移到某元素之上onmouseout:鼠标从某元素离开键盘事件:
onkeydown:按下键盘onkeyup:松开onkeypress:按下并松开其他
onchange 域的内容改变onselect 文本选中事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
使用原因:
在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;原理: 利用事件冒泡,子元素的事件会冒泡到父级元素上,所以可以将子元素时间委托给父级代执行事件。
使用方法:
Event对象提供了一个属性叫target,可以返回事件的目标节点,即事件源,即target可以表示为当前的事件操作的dom。 标准浏览器用ev.targetIE浏览器用event.srcElement可以使用nodeName来获取具体是什么标签名(返回的是大写) oUl.onclick = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; if(target.nodeName.toLowerCase() == 'li'){ alert(123); alert(target.innerHTML); } }