new操作符的执行步骤和代码实现
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function() {
alert(this.name)
}
}
function create(Fn,argArr) {
let obj = new Object();
obj.__proto__ = Fn.prototype;
Fn.apply(obj,argArr);
return obj;
}
var p2 = create(Person, ['biao', 22])
p2.say()
JS改变this指向
function fn1(x,y){
console.log(this);
}
var obj1 = {
name:"111"
}
fn1.call(obj1,1,2);
function fn2(x,y){
console.log(this);
}
var obj2 = {
name:"222"
}
fn2.apply(obj2,[1,2]);
function fn3(x,y){
console.log(this);
}
var obj3 = {
name:"333"
}
fn3.bind(obj3,1,2)();
写一个function,清除字符串前后的空格
String.prototype.trim= function(){
return this.replace(/^\s+/,"").replace(/\s+$/,"");
}
function trim(str) {
if (str & typeof str === "string") {
return str.replace(/(^s*)|(s*)$/g,"");
}
}
内存泄露问题
~~~ 引发内存泄露的情况:
1.全局变量(window变量 & 全局函数内的this='123')
2.闭包
3.事件监听(对同一个事件重复监听,但是忘记移除,会导致内存泄露)
4.其他原因(console.log & setInterval)
~~~ 如何检查内存泄露:
1.使用Chrome的开发者工具profiles来进行快照对比。
2.Node环境下,可以用Node提供的process.memoryUsage()方法来检查内存泄露
~~~ 如何处理内存泄漏
1.变量导致的内存泄露,将变量清除 a = null 即可
2.事件监听导致的内存泄露,监听后移除即可
JS闭包
成立条件:
1. 在函数 A 内部直接或间接返回一个函数 B;
2. B 函数内部使用着函数 A 的私有变量;
3. 函数 A 外部有一个变量接受着它的返回值,即函数 B
代码实现:
function A(){
var local = 1
function B(){
local++
return local
}
return B
}
var func = A()
func()
作用:避免变量被全局污染
缺点:无法垃圾回收,容易造成内存泄露
JS异步解决方案
-- 1. 传统回调函数(callback)
优点:简单,容易理解和 部署。
缺点:不利于代码的阅读,和维护,各部分之间高度耦合,流程会很混乱,
-- 2. 事件监听(on,bind,listen,addEventListener,observe)
优点:比较容易理解,可以绑定多个事件,每一个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。
缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰
-- 3. Promise
-- 4. Gengerator
-- 5. async await
-- 6. node.js中 nextTick setImmidate
-- 7. 第三方库 async.js
JS的深浅拷贝
var newObj = Object.assgin({}, obj)
1. JSON.parse(JSON.stringify(obj))
2.常规简单的深拷贝
var deepClone = function(currobj){
if(typeof currobj !== 'object'){
return currobj;
}
if(currobj instanceof Array){
var newobj = [];
}else{
var newobj = {}
}
for(var key in currobj){
if(typeof currobj[key] !== 'object'){
newobj[key] = currobj[key];
}else{
newobj[key] = deepClone(currobj[key])
}
}
return newobj
}
垃圾回收机制 (标记清除 + 引用计数)
概念定义:
1. js中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数……所有这些都需要内存。当不再需要某样东西时会发生垃圾回收;
2. 一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除
代码示例:
1. 引用:
var obj={name:'zhangsan'}//obj变量引用堆的{name:'zhangsan'}
obj=null
2. 两个引用:
var obj={name:'zhangsan'}
var admin=obj
obj=null
admin=null
内部算法:
// 基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:
1.垃圾回收器获取根并“标记”(记住)它们。
2.然后它访问并“标记”所有来自它们的引用。
3.然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
以此类推,直到有未访问的引用(可以从根访问)为止。
4.除标记的对象外,所有对象都被删除。
JS引擎应用了许多优化:
分代回收:对象分为两组:“新对象”和“旧对象”。许多对象出现,完成它们的工作并迅速结 ,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,并且很少接受检查。
增量回收:如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。
空闲时间收集:垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响。
JS代码执行机制
执行顺序:
1. 同步任务 newPromise 和 console 顺序执行
2. 异步微任务 process.nextTick(Node独有) , promise.then
3. 异步宏任务整体代码 setTimeout , setInterval , setImmediate , I/O , UIrendering
关于JavaScript:
js是一门单线程语言,一切js版的‘多线程’都是用单线程模拟起来的。
js事件循环(Event Loop):
同步任务先进入主线程,异步任务进入eventTable注册函数;
当指定的事件完成时,Event Table会将这个函数移入Event Queue
主线程内的任务执行完毕为空后,会去Event Queue读取对应的函数,进入主线程运行
setTimeout
task()进入Event Table并注册,计时开始
执行sleep()
3秒到了,计时事件完成,task()进入Event Queue,此时sleep()仍在执行
sleep()执行完毕
task()从Event Queue进入主线程并执行
setInterval
setInterval会每隔指定时间将注册的函数置入Event Queue
Promise 与 process.nextTick(callback)
宏任务 整体代码script setTimeout setInterval
微任务 Promise.then process.nextTick
不同类型的任务会进入不同的EventQueue
JS继承
function Animal(color){
this.color= color
this.type = ["cat","dog"]
console.log("Animal Class")
}
Animal.prototype.getColor = function(){
return this.color
}
function Dog(name){
Animal.apply(this,[...arguments])
this.name = name
}
Dog.prototype = new Animal()
(上面加起来)
手写bind的实现
Function.prototype.myBind = function(thisArg) {
if (typeof this !== 'function') {
return
}
var _self = this
var args = Array.prototype.slice.call(arguments, 1)
var fnNop = function () {}
var fnBound = function () {
var _this = this instanceof _self ? this : thisArg
return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments)))
}
if (this.prototype) {
fnNop.prototype = this.prototype;
}
fnBound.prototype = new fnNop();
return fnBound;
}
http & https
1.传输信息安全性不同:
http:超文本明文传输,容易被截取报文信息;
https:安全性的ssl加密传输协议,对通信加密
2.连接方式不同
http协议:http的连接很简单,是无状态的。
https协议:是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议。
3.端口不同
http:80端口
https:443端口
4.证书申请方式不同
http协议:免费申请。
https协议:需要到 数字证书认证机构(ca)申请证书,一般免费证书很少,需要交费。
HTTP1.0 & HTTP1.1 & HTTP2.0 的区别
* HTTP1.0
特点:
1. 无状态、无连接
一种无状态、无连接的应用层协议。
浏览器的每次请求都需要与服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接(无连接);
服务器不跟踪每个客户端也不记录过去的请求(无状态)。
缺点:(为了解决这些问题,1.1版本产生了)
1. 无连接的特性导致最大的性能缺陷就是无法复用连接。每次发送请求的时候,都需要进行一次TCP的连接;
而TCP的连接释放过程又是比较费事的。这种无连接的特性会使得网络的利用率非常低
2. 队头阻塞,由于HTTP1.0规定下一个请求必须在前一个请求响应到达之前才能发送。
假设前一个请求响应一直不到达,那么下一个请求就不发送,同样的后面的请求也给阻塞了
* HTTP1.1
特点:
1. 持久连接
长连接,HTTP1.1增加了一个Connection字段,通过设置Keep-Alive可以保持HTTP连接不断开,
避免了每次客户端与服务器请求都要重复建立释放建立TCP连接,提高了网络的利用率。
如果客户端想关闭HTTP连接,可以在请求头中携带Connection: false来告知服务器关闭请求
2. 请求管道化
其次,是HTTP1.1支持请求管道化(pipelining)。基于HTTP1.1的长连接,使得请求管线化成为可能。
管线化使得请求能够“并行”传输。举个例子来说,假如响应的主体是一个html页面,页面中包含了很多img,
这个时候keep-alive就起了很大的作用,能够进行“并行”发送多个请求(注意这里的“并行”并不是真正意义上的并行传输)
也就是说,HTTP管道化可以让我们把先进先出队列从客户端(请求队列)迁移到服务端(响应队列)
3.增加缓存处理(新的字段如cache-control)
此外,HTTP1.1还加入了缓存处理(强缓存和协商缓存)新的字段如cache-control,
4.增加Host字段、支持断点传输等
支持断点传输,以及增加了Host字段(使得一个服务器能够用来创建多个Web站点)
缺点:
1. 可见,HTTP1.1还是无法解决队头阻塞,只有等到html响应的资源完全传输完毕后,css响应的资源才能开始传输。
也就是说,不允许同时存在两个并行的响应
* HTTP2.0
特点:
1. 二进制分帧
HTTP2.0通过在应用层和传输层之间增加一个二进制分帧层,突破了HTTP1.1的性能限制、改进传输性能;
简单来说,HTTP2.0只是把原来HTTP1.x的header和body部分用frame重新封装了一层而已
2. 多路复用(连接共享)
流(stream):已建立连接上的双向字节流。
消息:与逻辑消息对应的完整的一系列数据帧。
帧(frame):HTTP2.0通信的最小单位,每个帧包含帧头部,至少也会标识出当前帧所属的流(stream id)。
HTTP2.0实现了真正的并行传输,它能够在一个TCP上进行任意数量HTTP请求。而这个强大的功能则是基于“二进制分帧”的特性
3. 头部压缩
在HTTP1.x中,头部元数据都是以纯文本的形式发送的,通常会给每个请求增加500~800字节的负荷
比如cookies,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,
既避免了重复header的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟
4. 服务器推送
服务器除了对最初请求的响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确的请求
在浏览器中输入URL并回车后都发生了什么
a. 传输协议
b. 服务器
c. 域名
d. 端口
e. 虚拟目录
f. 文件名
g. 锚
h. 参数
DNS解析(域名解析),DNS实际上是一个域名和IP对应的数据库。
查询浏览器缓存
检查系统缓存,检查hosts文件,这个文件保存了一些以前访问过的网站的域名和IP的数据。它就像是一个本地的数据库。如果找到就可以直接获取目标主机的IP地址了。没有找到的话,需要
检查路由器缓存,路由器有自己的DNS缓存,可能就包括了这在查询的内容;如果没有,要
查询ISP DNS 缓存:ISP服务商DNS缓存(本地服务器缓存)那里可能有相关的内容,如果还不行的话,需要,
递归查询:从根域名服务器到顶级域名服务器再到极限域名服务器依次搜索哦对应目标域名的IP。
第一次握手:客户端向服务器端发送请求(SYN=1) 等待服务器确认;
第二次握手:服务器收到请求并确认,回复一个指令(SYN=1,ACK=1);
第三次握手:客户端收到服务器的回复指令并返回确认(ACK=1)。
比如要通过get请求访问“http:
请求网址(url):http:
请求方法:GET
远程地址:IP
状态码:200 OK
Http版本: HTTP/1.1
请求头: ...
响应头: ...
a. 浏览器会解析html源码,然后创建一个 DOM树。
在DOM树中,每一个HTML标签都有一个对应的节点,并且每一个文本也都会有一个对应的文本节点。
b. 浏览器解析CSS代码,计算出最终的样式数据,形成css对象模型CSSOM。
首先会忽略非法的CSS代码,之后按照浏览器默认设置——用户设置——外链样式——内联样式——HTML中的style样式顺序进行渲染。
c. 利用DOM和CSSOM构建一个渲染树(rendering tree)。
渲染树和DOM树有点像,但是是有区别的。
DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。
而且一大段文本中的每一个行在渲染树中都是独立的一个节点。
渲染树中的每一个节点都存储有对应的css属性。
d. 浏览器就根据渲染树直接把页面绘制到屏幕上。
重要的http头部信息
request:
cookie:// 客户端凭证,携带登录信息,可设置有效期
user-agent:// 用来判断浏览器信息,app的环境,是否是微信等
refer:// 从哪里发起访问,作用:防盗链
Content-Type:// 请求类型
response:
set-cookie:
content-encoding:gzip:
access-control-allow-origin:
cookie:access-control-allow-credentials:
import/export & require/exports
require,2009,commonjs
import,2015,ES5/6
require运行时动态加载
import是静态编译
require/exports输出的是一个值的拷贝
import/export 模块输出的是值的引用
(文件引用的模块值改变,require 引入的模块值不会改变,而 import 引入的模块值会改变)
const fs = require('fs') import fs from 'fs'
exports.fs = fs export {readFile, read}
module.exports = fs export default fs
ES6模块可以在import引用语句前使用模块,
CommonJS则需要先require引用后使用
import/export只能在模块顶层使用,不能在函数、判断语句等代码块之中引用;
require/exports可以
import/export 导出的模块默认调用严格模式
require/exports 默认不使用严格模式,可以自定义是否使用严格模式
JS设计模式 - 单例模式
定义:保证一个类仅有一个实例,并且提供一个可以访问它的访问点
实现:用一个变量来标识实例是否已经存在,如果存在,则直接返回已经创建好的实例,反之就创建一个对象
场景:模态框、浏览器window对象
代码:
class Singleton {
constructor(name) {
this.name = name;
}
getName() {
console.log(this.name);
}
static getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
Singleton.instance = null;
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
a.getName();
b.getName();
console.log(a===b);
JS设计模式 - 观察者模式
定义:当一个对象的状态发生变化时,所有依赖于他的对象都将得到通知
实现:指定一个发布者,给发布者添加一个缓存列表,列表用于存放回调函数以便通知订阅者,
发布者发布消息的时候,会遍历这个缓存列表,触发里面存放的订阅者回调函数
代码:
myDiv.addEventListener('click', function(){
console.log("myDiv被点击了")
});
myDiv.click();
class SalesOffices{
constructor(){
this.clientList = [];
}
listen(fn){
this.clientList.push(fn);
}
trigger(){
for(var i=0;i<this.clientList.length;i++){
var fn = this.clientList[i];
fn.apply(this,arguments);
}
}
}
var salesOffices = new SalesOffices();
salesOffices.listen(function(price){
console.log("小明" + price);
});
salesOffices.listen(function(price){
console.log("小红" + price);
});
salesOffices.trigger("你好,今天的房价为2万一平!");
JS设计模式 - 工厂模式
定义:不暴露创建对象的具体逻辑,而是将将逻辑封装在一个函数中,这个函数被视为一个工厂
分类:简单工厂,工厂方法,抽象工厂
区别:简单工厂是将创建对象的步骤放在父类进行,工厂方法是延迟到子类中进行,它们两者都可以总结为:“根据传入的字符串来选择对应的类”,
而抽象工厂则是:“用第一个字符串选择父类,再用第二个字符串选择子类”
1.实现简单工厂 (ES5)
var UserFactory = function (role) {
function Admin() {
this.name = "管理员",
this.viewPage = ['首页', '查询', '权限管理']
}
function User() {
this.name = '普通用户',
this.viewPage = ['首页', '查询']
}
switch (role) {
case 'admin':
return new Admin();
break;
case 'user':
return new User();
break;
default:
throw new Error('参数错误, 可选参数: admin、user');
}
}
var admin = UserFactory('admin');
var user = UserFactory('user');
2. 实现工厂方法 (ES5)
var UserFactory = function (role) {
if (this instanceof UserFactory) {
var s = new this[role]();
return s;
} else {
return new UserFactory(role);
}
}
UserFactory.prototype = {
Admin: function () {
this.name = "管理员",
this.viewPage = ['首页', '查询', '权限管理']
},
User: function () {
this.name = '用户',
this.viewPage = ['首页', '查询']
}
}
var admin = UserFactory('Admin');
var user = UserFactory('User');
什么是高阶函数?简单实现一个?
定义:英文叫Higher-order function。JavaScript的函数其实都指向某个变量。既然变量可以指向函数,
函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
形式:接收函数,包装后返回函数
代码:
function add(x, y, f) {
return f(x) + f(y);
}
add(-5, 6, Math.abs);
为什么Commonjs不适用于浏览器
// var math = require('math');
// math.add(2, 3);
第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。
也就是说,如果加载时间很长,整个应用就会停在那里等。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间
但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,
可能要等很长时间,浏览器处于"假死"状态。
因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。
这就是AMD规范诞生的背景。
JS数据类型扩展题
/* 1. 对象类型转换 */
/* 当两个类型不同时进行==比较时,会将一个类型转为另一个类型,然后再进行比较。
比如Object类型与Number类型进行比较时,Object类型会转换为Number类型。
Object转换为Number时,会尝试调用Object.valueOf()和Object.toString()来获取对应的数字基本类型。*/
var a = {
i: 1,
toString: function () {
return a.i++
}
}
console.log(a == 1 && a == 2 && a == 3) // true
/* 2. 数组类型转换 */
/* 与上面这个类型转换一样,数组调用toString()会隐含调用Array.join()方法 而数组shift方法的用法:
shift() 方法用于把数组的第一个元素从其中删除,
并返回第一个元素的值。如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。
请注意,该方法不创建新数组,而是直接修改原有的 数组。
所以我们可以看到 a == 1时会调用toString(),toString()调用join(),join()等于shift,则转换为Number类型后为1.
var a = [1, 2, 3]
a.join = a.shift
console.log(a == 1 && a == 2 && a == 3)
/* 3. defineProperty */
/* 使用一个defineProperty,让 a 的返回值为三个不同的值。 */
var val = 0
Object.defineProperty(window, 'a', { // 这里要window,这样的话下面才能直接使用a变量去 ==
get: function () {
return ++val
}
})
console.log(a == 1 && a == 2 && a == 3) // true