很久没有写博客了…因为这段时间都没有做新的项目,一直在复习前端的基础知识,大部分的(面试)知识点都归类为体系放入了印象笔记里。但因为还是有些繁杂,所以刚好再整理一次发到博客上来。
注:部分图片来自网络
知识点是仅个人根据面经以及自己的面试经历整理,仅供参考
文章目录
数据类型分类
基本数据类型(存放在栈中)
- number(包括整数,浮点数,NaN)
- boolean
- null
- undefined
- string
- symbol(es6新增,表示独一无二的值)
基本数据类型存放在栈中,数据大小确定,直接按值存放,所以可以直接按值访问
引用数据类型(存放在堆内存中)
- object(包括正则对象,函数,数组等等)
前面说到变量是储存在栈中的,但是对于引用数据类型来说,存储在栈中的仅仅是一个指针,这个指针指向堆内存(真正的对象变量)。
如上图,从obj1
复制一个对象到obj2
,仅仅是复制了一个指针而已,指向的还是同一个东西。这就是js中,引用数据类型的浅拷贝。
引用数据类型的深拷贝与浅拷贝
对于基本数据类型,复制变量相当于把值重新赋给新变量;但对于引用数据类型来说,仅仅拷贝指针,即仅仅实现了浅拷贝。
浅拷贝
对于仅仅是复制了引用(地址),即复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响。
在开发工程过程中,肯定需要避免这种情况,更多的是希望能够完全复制出一个新对象。即深拷贝,即在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响。
综上:
深浅拷贝的主要区别:
复制的是引用(地址)还是复制的是实例。
如何实现深拷贝
-
Array的slice 和 concat 方法 和 jQuery 中的 extend 复制方法。都只能复制第一层的值,对于第一层是深拷贝,但是到第二层之后就只复制引用,所以不是真正的深拷贝。
-
真正的深拷贝
- JSON 对象的 parse 和 stringify
- JSON 对象中的 stringify 可以把一个 js 对象序列化为一个 JSON 字符串,parse 可以把 JSON 字符串反序列化为一个 js 对象,这两个方法实现的是深拷贝。(两个概念,序列化与反序列化)
数据类型判断
1. typeof
- 可以判断:number, boolean, string, function, undifined, object
typeof(null) -> object
typeof([]) -> object
2. instanceof
- 根据原型链来检验数据类型
- 原理:判断一个对象与构造函数是否在一个原型链上
- 只能检验出使用new声明的数据类型,并且不能区别
undefined
和null
3. constructor
bool.constructor === Boolean
//true
- constructor不能判断undefined和null
- 使用它是不安全的,因为
contructor
的指向是可以改变的a.constructor = Object
4. Object.prototype.toStrirng.call
在任何值上调用 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
方法可以将其它进制转换为十进制,只需要给该方法传入需要转换的字符串和该字符串的进制表示两个参数即可。
隐式转换
利用操作符进行转换都可以看做隐式转换。
例如:
- 运算中,+号,数字隐式转换成字符串。
- 其余的运算符号是字符串隐式转换成数字。
关于数据类型的其他问题
1. 类数组
类数组是有数组行为的对象
对于一个普通的对象来说,如果它:
- 所有元素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方法的返回结果
判断数组与类数组:
var arr = [1,1,4,6]
var lis = document.getElementsByTagName('div')
// 第一种方式 instanceof
console.log(arr instanceof Array);
console.log(lis instanceof Array);
// 第二种方式
console.log(arr.constructor === Array)
console.log(lis.constructor === Array)
// 第三种方式
console.log(Object.prototype.toString.call(arr) === "[object Array]")
console.log(Object.prototype.toString.call(lis) === "[object Array]")
// 使用ES5提供的方法
console.log(Array.isArray(arr))
console.log(Array.isArray(lis))
类数组与数组的转换
-
类数组转为数组
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
- 使用
2. 基本数据类型的包装对象
var a = 'string'
typeof a //String
a.length //6
虽然变量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;
3. 浮点数的比较问题
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)); //true
4. NaN
NaN
即 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'; // NaN
2. 运算符运算失败
在做运算(减乘除等)时,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()
isNaN方法检查一个值是否能被 Number() 成功转换 。 如果能转换成功,就返回 false,否则返回 true 。
isNaN(NaN) // true 不能转换
isNaN('123') // false 能转换
isNaN('abc') // true 不能转换
isNaN('123ab') // true 不能转换
NaN !== NaN
因为NaN 就是除了数字的任意值,但绝不是确切的某一个值,所以它是一个范围,而不是一个确定的值。所以NaN !== NaN
5. 变量提升与函数提升
变量提升
变量提升即:将变量声明提升到它所在作用域的最开始的部分。
例如:
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(){}
6. 假值
JavaScript中有 6 个值为“假”,这六个值是
- false
- null
- undefined
- 0(包括+0, -0)
- ’ ’ (空字符串)
- NaN
这些假值可以被强制类型转换成false
。
DOM事件类
事件级别
- DOM0
element.onclick=function(){}
- DOM2
element.addEventListener('click',function(){},false)
- DOM3
element.addEventListener('keyup',function(){},false)
addEventListner
与onclick
的区别
-
事件冒泡与捕获
- 对于
addEventListner
来说,第三个参数:
-true
- 事件句柄在捕获阶段执行
-false
- 默认。事件句柄在冒泡阶段执行 - 而
onclick
只能使用事件冒泡
- 对于
-
是否能重复
onclick
一次只能对一个元素绑定一个事件处理程序,如果绑定多个,会被覆盖掉。- 而
addEventListner
可以给一个事件注册多个listener
-
addEventListner
对任何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>
上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。 -
冒泡阶段:事件又传播回文档。
Event对象
event.preventDefault()
- 组织默认行为即通知浏览器不要执行与事件关联的默认动作。
event.stopPropagation
- 阻止冒泡 - 不再派发事件。
event.stopImmediatePropagation
- 除了可以阻止事件冒泡之外,还可以把这个元素绑定的同类型事件也阻止了。
event.currentTarget
- 返回绑定事件的元素
event.target
- 返回触发事件的元素
常见事件函数
-
点击事件:
- 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.target
- IE浏览器用
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);
}
}