【前端必备】三、JS篇
1.运算符与隐式类型转换
类型判断:
typeof |
constructor.toString().indexOf() |
---|---|
NaN是number Array、Date、Null都是Object function是function 未定义变量都是undefined |
"John".constructor // 返回函数 String() (3.14).constructor// 返回函数 Number() false.constructor// 返回函数 Boolean() [1,2,3,4].constructor// 返回函数Array() {name:'John', age:34}.constructor // 返回函数 Object() new Date().constructor// 返回函数 Date() function () {}.constructor// 返回函数 Function() |
类型转换:
- 对象转基本类型:
先调用valueOf()再调用toString() - 其他简单类型-->String:
方法 | 注意 |
---|---|
变量+"" | |
String(变量) | |
变量.toString() | ull和undefined这两个值没有toString()方法, Number.toString(2);将数字转成二进制 ArrayObject.toString()会转为逗号分隔的列表内容 |
- 其他数据类型-->Number:
方法 | 注意 |
---|---|
Number(变量) | true-->1 ; false-->0 ; null-->0 ;""或空格串-->0; undefined-->NaN;非纯数字串-->NaN; |
parseInt(变量,进制) | 只保留开头数字,小数自动截断,非字符串会先自动转字符串。 两个参数时做进制转换 |
parseFloat(变量) | 可以获得有效的小数部分 |
- 转换为Boolean:
0、-0、NaN、空串、null、undefined会转成false,其他都转成true - 伪数组转数组:
Array.prototype.slice.call(arguments)
隐式类型转换:
算术运算符 - * / + |
自动转换为Number 任何值和NaN计算结果都是NaN 任何的值和字符串做加法运算都会先转换为字符串,然后再做拼串操作 如[1,2]+[2,3]="1,22,3" 'a'+ +'b'="aNaN" |
一元运算符 typeof + - ++ -- |
对一个其他的数据类型使用+ - ++ -- 会将其转换为数字 |
逻辑运算符 && || ! |
能参与逻辑运算的都是布尔值 非布尔值会先转换成布尔值 |
关系运算符 && || ! |
*比较:非数值比较会先转换成数字 两个字符串比较不会转换,而是比较它们的unicode编码 任何值与NaN作比较都是false。(只有NaN自己不等于自己) *判等:==不同类型会转为相同类型(大部分时候转为数字) ===不会做类型转换 |
调用非函数,或者读取null或者undefined的属性时,会报错 :
isNaN("foo"); // true
isNaN(undefined); // true
isNaN({}); // true
2.创建对象的方法
- new Object();
- 工厂函数(一个返回Object的函数)
- 构造函数,必须使用new来调用,this指向新建的对象。
new 一个构造函数的执行流程
(1)开辟内存空间,存储新创建的对象
(2)链接到原型,
(3)绑定this到新建对象,执行构造函数
(4)将新建的对象作为返回值返回
通过同一个构造函数实例化的多个对象具有相同的原型对象。
实例对象有一个__proto__属性,指向该实例对象对应的原型对象
实例.__proto__.constructor==构造函数 //true
构造函数有prototype属性,实例有__proto__属性
实例.__proto__==构造函数.prototype
试着实现一下instanceof:
function instanceof(left, right) {
// 获得类型的原型
let prototype = right.prototype
// 获得对象的原型
left = left.__proto__
// 判断对象的类型是否等于类型的原型
while (true) {
if (left === null)
return false
if (prototype === left)
return true
left = left.__proto__
}
}
3.闭包
闭包是指有权访问另一函数作用域中的变量的函数。
换句话说,在函数内定义一个嵌套的函数时,就构成了一个闭包, 它允许嵌套函数访问外层函数的变量。 通过返回嵌套函数,允许你维护对外部函数中局部变量、参数、和内函数声明的访问。
这种封装允许你在外部作用域中隐藏和保护执行环境,并且暴露公共接口,进而通过公共接口执行进一步的操作。
缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。
解决:能不用闭包就不用,及时释放。(将引用置为null)
经典面试题:
循环中使用闭包解决 var 定义函数的问题:
- 解决方法一,使用闭包:
(包装了一层立即执行函数,这样就不会污染全局变量了)
- 解决方法二,使用let:
- 解决方法三,使用setTimeout的第三个参数:
4.作用域和上下文
作用域(Scope)和上下文(Context)
作用域决定了代码区块中变量和其他资源的可见性。它指一个变量的作用范围。相对于上下文对象是静态的, 在编写代码时就确定了。
上下文(context)是指 this 在同一作用域内的值,是代码的执行环境。
执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放。
有三种类型的ECMAScript代码:全局代码,函数代码和eval代码。代码执行在它的执行上下文里。有唯一的全局上下文,以及可能有多个函数和eval上下文。
1.在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2.在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
3.在函数执行上下文创建后, 将其添加到栈中(压栈)
4.在当前函数执行完后,将栈顶的对象移除(出栈)
5.当所有的代码执行完后, 栈中只剩下window
5.内存溢出和内存泄漏
内存溢出:一种程序运行出现的错误。当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误。
内存泄漏:占用的内存没有及时释放。
注意,内存泄露的次数积累多了,就容易导致内存溢出。
常见的内存泄露:
•意外的全局变量
•没有及时清理的计时器或回调函数
6.深浅拷贝
将引用复制改为值复制:
- Object.assign(target, ...sources)
将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 不会跳过那些值为 null 或 undefined 的源对象。
只能解决第一层的问题 - 展开运算符 let b = {...a}
只能解决一层 - JSON.parse(JSON.stringify(object))
会忽略 undefined、symbol
不能序列化函数
不能解决循环引用(父子循环引用、兄弟循环引用)的对象
/**
* 对象克隆
* 支持基本数据类型及对象
* 递归方法
*/
// 方法一:
function clone(obj) {
var o;
switch (typeof obj) {
case "undefined":
break;
case "string":
o = obj + "";
break;
case "number":
o = obj - 0;
break;
case "boolean":
o = obj;
break;
case "object": // object 分为两种情况 对象(Object)或数组(Array)
if (obj === null) {
o = null;
} else {
if (Object.prototype.toString.call(obj).slice(8, -1) === "Array") {
o = [];
for (var i = 0; i < obj.length; i++) {
o.push(clone(obj[i]));
}
} else {
o = {};
for (var k in obj) {
o[k] = clone(obj[k]);
}
}
}
break;
default:
o = obj;
break;
}
return o;
}
7.异步与同步
js 是单线程,而且有一个任务队列:全部的同步任务执行完毕后,再来执行异步任务。
同步任务在主线程上排队执行,异步任务在任务队列等待,主线程上的同步任务执行完了,才会从任务队列取出异步任务执行。
常见的异步操作:网络请求、定时任务、事件绑定、promise
异步编程的4种方式:
- 回调函数
- 事件监听
f1.on('done', f2);
f1.trigger('done');
- 发布/订阅
jQuery.subscribe("done", f2);
jQuery.publish("done");
jQuery.unsubscribe("done", f2);
- promise对象
8.伪数组
特性:具有length属性、按索引方式存储数据、不具有数组的push,pop等方法。
例子:函数的arguments参数,调用getElementsByTagName,document.childNodes之类的返回的NodeList对象。
将伪数组转为数组:
function log(){
var args = Array.prototype.slice.call(arguments);
//为了使用unshift数组方法,将argument转化为真正的数组
args.unshift('(app)');
console.log.apply(console, args);
};
9.arguments的callee和caller属性
两个属性都是指针
arguments.callee指向当前函数,
arguments.caller指向当前函数的父函数
可用于递归函数中的自调用,实现函数执行与函数名的解耦合。
10.this、call、apply、bind
this 表示当前对象的一个引用
• 在方法中,this 表示该方法所属的对象。
• 如果单独使用,this 表示全局对象。
• 在函数中,this 表示全局对象。
• 在函数中,在严格模式下,this 是未定义的(undefined)。
• 在事件中,this 表示接收事件的元素。
类似 call() 和 apply() 方法可以将 this 引用到任何对象。
call、apply是立即调用函数,apply的第二个参数是参数数组。
persion1.say.call(p2, "实验小学", "六年级");
persion1.say.apply(p2, ["实验小学", "六年级"]);
将第一个参数p2作为say方法内this的指向,后面的参数作为say函数的参数。
bind传参与call相同,但bind返回一个函数,要加()才能立即执行。
persion1.say.bind(p2, "实验小学", "六年级")();
11.事件机制
事件触发的三个阶段 :
路径:Window <——> Document <——> <html> <——> … <——> 事件触发处
- 捕获阶段:遇到注册的捕获事件会触发
- 目标阶段:传播到事件触发处时触发注册的事件
- 冒泡阶段:遇到注册的冒泡事件会触发
如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。
注册事件:
属性注册:
<button onlick="sayHello()">点击</button>
方法注册:
element.addEventListener(event, function, useCapture);
//usecapture默认false冒泡,true捕获
element.attachEvent(event,function); //IE中
EventTarget.onEventName=function(e){};
注销事件:
removeEventListener(event,function,capture/bubble);
detachEvent(event,function);
- 事件属性赋值为null
阻止事件冒泡:
box3.onclick = function (event) {
alert("child");
//阻止冒泡
event = event || window.event;
if (event && event.stopPropagation) {
event.stopPropagation();//w3c(火狐、谷歌、IE11)
} else {
event.cancelBubble = true;//IE10以下
}
}
事件委托(事件代理):
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该委托给父节点
优点:减少内存消耗,提高性能。
12.BOM与DOM
BOM与DOM的关系:
系统对话框:
alert(); //不同浏览器中的外观是不一样的
confirm(); //兼容不好
prompt(); //不推荐使用
window操作窗口
window.open(url,target,param)
window.close()
新窗口.moveTo(5,5)
新窗口.resizeTo()
window.resizeBy()
location对象:
window.location可以简写成location。location相当于浏览器地址栏,可以将url解析成独立的片段。
- 属性
- href:跳转
- hash 返回url中#后面的内容,包含#
- host 主机名,包括端口
- hostname 主机名
- pathname url中的路径部分
- protocol 协议 一般是http、https
- search 查询字符串
- 方法
- location.assign():改变浏览器地址栏的地址,并记录到历史中
设置location.href 就会调用assign()。一般使用location.href 进行页面之间的跳转。 - location.replace():替换浏览器地址栏的地址,不会记录到历史中
- location.reload():重新加载
- location.assign():改变浏览器地址栏的地址,并记录到历史中
navigator对象:
window.navigator的一些属性 可以获取客户端的一些信息。
appCodeName appName appVersion cookieEnabled platform userAgent systemLanguage
history对象:
window.history 包含用户(在浏览器窗口中)访问过的 URL。
方法 | 描述 |
---|---|
back() | 加载 history 列表中的前一个 URL |
forward() | 加载 history 列表中的下一个 URL |
go() | 加载 history 列表中的某个具体页面 |
screen对象:
Screen 对象包含有关客户端显示屏幕的信息。
availHeight availWidth colorDepth height width pixelDepth