JS的单线程模型和事件循环机制
JS的单线程模型意味着,在执行JS时只有一个主线程,每个任务必须顺序执行。如果当前任务执行时间过长,会导致接下来的所有任务都处于阻塞状态,进而导致浏览器卡死等我们不希望看到的状况。为了解决这一问题,事件循环机制(Event Loop)被发明出来。
事件循环机制中,负责执行JS脚本的单线程我们称为主线程,在内存中表现为一个执行栈,JS只通过主线程执行任务。异步任务被挂起,存储在堆中,当异步任务准备就绪,它对应的事件便进入任务队列。主线程首先执行同步任务,然后查看任务队列是否有就绪的异步任务(或者时间到了的异步任务),调用相应的回调函数执行,直到任务队列为空。至此即完成一个事件循环。
数组扁平化
将多层次的数组转变成单层的数组
function flatten(arr) {
return arr.reduce(function (pre,item) {
return pre.concat(item instanceof Array?flatten(item):item)
},[])
}
.sort()
默认按字典序排序。如果要对数值排序:.sort(function(a,b){ return a-b })
获取dom节点的兄弟节点
firstChild,lastChild,nextSibling,previousSibling都会将空格或者换行当做节点处理,但是有代替属性
所以为了准确地找到相应的元素,会用
firstElementChild,
lastElementChild,
nextElementSibling,
previousElementSibling
兼容的写法,这是JavaScript自带的属性。
但坏消息是IE6,7,8不兼容这些属性。IE9以上和火狐谷歌支持。
js实现二叉查找树
function BinarySearchTree () {
var Node = function(key) {
this.key = key,
this.left = null,
this.right = null
}
var root = null
//插入节点
this.insert = function(key) {
var newNode = new Node(key)
if(root === null) {
root = newNode
} else {
insertNode(root, newNode)
}
}
var insertNode = function(node, newNode) {
if (newNode.key <= node.key) {
if (node.left === null) {
node.left = newNode
}else {
insertNode(node.left, newNode)
}
}else {
if (node.right === null) {
node.right = newNode
}else {
insertNode(node.right, newNode)
}
}
}
//实现中序遍历
this.inOrderTraverse = function() {
inOrderTraverseNode(root)
}
var inOrderTraverseNode = function(node) {
if (node !== null) {
inOrderTraverseNode(node.left)
console.log(node.key)
inOrderTraverseNode(node.right)
}
}
// 实现先序遍历
this.preOrderTraverse = function() {
preOrderTraverseNode(root)
}
var preOrderTraverseNode = function(node) {
if (node !== null) {
console.log(node.key)
preOrderTraverseNode(node.left)
preOrderTraverseNode(node.right)
}
}
// 实现后序遍历
this.postOrderTraverse = function() {
postOrderTraverseNode(root)
}
var postOrderTraverseNode = function(node) {
if (node !== null) {
postOrderTraverseNode(node.left)
postOrderTraverseNode(node.right)
console.log(node.key)
}
}
}
Null和Undefined
var oValue;
alert(oValue == undefined);//output "true"
这段代码显示为true,代表oVlaue的值即为undefined,因为我们没有初始化它。
alert(null== document.getElementById('notExistElement'));
当页面上不存在id为"notExistElement"的DOM节点时,这段代码显示为"true",因为我们尝试获取一个不存在的对象。
JS实现快速排序
大致分三步:
1、找基准(一般是以中间项为基准)
2、遍历数组,小于基准的放在left,大于基准的放在right
3、递归
function quickSort(arr){
//如果数组<=1,则直接返回if(arr.length<=1){return arr;}
var pivotIndex=Math.floor(arr.length/2);//找基准,并把基准从原数组删除varpivot=arr.splice(pivotIndex,1)[0];
//定义左右数组va rleft=[];
var right=[];
//比基准小的放在left,比基准大的放在rightfor(vari=0;i
if(arr[i]<=pivot){
left.push(arr[i]);
}
else{
right.push(arr[i]);
}
}
//递归return quickSort(left).concat([pivot],quickSort(right));
}
js手动实现call,apply,bind
Function.prototype.myCall =function(obj, ...args){
//我们要让传入的obj成为, 函数调用时的this值.
obj._fn_ =this;//在obj上添加_fn_属性,值是this(要调用此方法的那个函数对象)。
obj._fn_(...args);//在obj上调用函数,那函数的this值就是obj.
deleteobj._fn_;// 再删除obj的_fn_属性,去除影响.
//_fn_ 只是个属性名 你可以随意起名,但是要注意可能会覆盖obj上本来就有的属性
}
Function.prototype.myApply =function(obj, ...args){
//我们要让传入的obj成为, 函数调用时的this值.
obj._fn_ =this;//在obj上添加_fn_属性,值是this(要调用此方法的那个函数对象)。
obj._fn_(args);//在obj上调用函数,那函数的this值就是obj.
deleteobj._fn_;// 再删除obj的_fn_属性,去除影响.
//_fn_ 只是个属性名 你可以随意起名,但是要注意可能会覆盖obj上本来就有的属性
}
Function.prototype.es6Bind = function(context, ...rest) {
if (typeof this !== 'function') throw new TypeError('invalid invoked!');
var self = this;
return function F(...args) {
if (this instanceof F) {
return new self(...rest, ...args)
}
return self.apply(context, rest.concat(args))
}
}
new的简单实现
function newFunc(constructor){
//第一步:创建一个空对象obj
var obj = {};
//第二步:将构造函数 constructor的原型对象赋给obj的原型
obj.__proto__ = constructor.prototype;
//第三步:将构造函数 constructor中的this指向obj,并立即执行构造函数内部的操作
constructor.apply(obj);
//第四步:返回这个对象
return obj;
}
保留小数
varnum =2.446242342;
num = num.toFixed(2); // 输出结果为 2.45
let、const、var 的区别
是否存在变量提升?
var声明的变量存在变量提升(将变量提升到当前作用域的顶部)。即变量可以在声明之前调用,值为undefined。
let和const不存在变量提升。即它们所声明的变量一定要在声明后使用,否则报ReferenceError错。
是否存在暂时性死区
let和const存在暂时性死区。即只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
是否允许重复声明变量
var允许重复声明变量。let和const在同一作用域不允许重复声明变量。
是否存在块级作用域
var不存在块级作用域(内层变量可能会覆盖外层变量,计数的循环变量泄露为全局变量)。let和const存在块级作用域。
是否能修改声明的变量
var和let可以。const声明一个只读的常量。
DOM事件流
即比如点击td元素时,由外至内层会经历捕获阶段,目标阶段,冒泡阶段三个阶段,相应地会触发路径元素上的事件
此外,addEventListener(type, listener[, useCapture])函数默认设置在冒泡阶段。
事件委托
在父元素上监听事件;通过event.target确定触发源。
substring和substr的区别
两者都是截取字符串。
相同点:如果只是写一个参数,两者的作用都一样:都是是截取字符串从当前下标以后直到字符串最后的字符串片段。
substr(startIndex);
substring(startIndex);
不同点:第二个参数
substr(startIndex,lenth): 第二个参数是截取字符串的长度(从起始点截取某个长度的字符串);
substring(startIndex, endIndex): 第二个参数是截取字符串最终的下标 (截取2个位置之间的字符串,‘含头不含尾’)。
window对象
子对象:
navigator对象(了解即可)
浏览器对象,通过这个对象可以判定用户所使用的浏览器,包含了浏览器相关信息。
screen对象(了解即可)
屏幕对象,不常用。
history对象(了解即可)
window.history 对象包含浏览器的历史。
location对象
window.location 对象用于获得当前页面的地址 (URL),并把浏览器重定向到新的页面。
localstorage和sessionstorage
前端路由
前端路由的两种模式: hash 模式和 history 模式
hash模式
这里的 hash 就是指 url 尾巴后的 # 号以及后面的字符。这里的 # 和 css 里的 # 是一个意思。hash 也 称作 锚点,本身是用来做页面定位的,她可以使对应 id 的元素显示在可视区域内。
由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的。
使用到的api:
window.location.hash ='qq'// 设置 url 的 hash,会在当前url后加上 '#qq'
varhash =window.location.hash// '#qq'
window.addEventListener('hashchange',function(){// 监听hash变化,点击浏览器的前进后退会触发})
history模式
已经有 hash 模式了,而且 hash 能兼容到IE8, history 只能兼容到 IE10,为什么还要搞个 history 呢?
首先,hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,hash 的传参是基于 url 的,如果要传递复杂的数据,会有体积的限制,而 history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中。
相关API:
window.history.pushState(state, title, url)// state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取// title:标题,基本没用,一般传 null// url:设定新的历史记录的 url。新的 url 与当前 url 的 origin 必须是一樣的,否则会抛出错误。url可以是绝对路径,也可以是相对路径。//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/
window.history.replaceState(state, title, url)// 与 pushState 基本相同,但她是修改当前历史记录,而 pushState 是创建新的历史记录
window.addEventListener("popstate",function(){// 监听浏览器前进后退事件,pushState 与 replaceState 方法不会触发 });
window.history.back()// 后退
window.history.forward()// 前进
window.history.go(1)// 前进一步,-2为后退两步,window.history.lengthk可以查看当前历史堆栈中页面的数量
history 模式改变 url 的方式会导致浏览器向服务器发送请求,这不是我们想看到的,我们需要在服务器端做处理:如果匹配不到任何静态资源,则应该始终返回同一个 html 页面。
用发布订阅监听pushstate和replacestate
class Dep{
constructor(name){
this.id =new Date()
this.subs = []
}
defined(){
this.subs.push(Dep.watch)
}
notify(){
this.subs.forEach(item=>{
item.update()
})
}
}
Dep.watch =null
class watcher{
constructor(name,fn){
this.name =name
this.callback = fn
}
update(){
this.callback()
}
}
var addHistoryMethod = (function (){
var mydep =new Dep()
return function (name) {
if(name =='historychange'){
return function (name,fn) {
var watch =new watcher(name,fn)
Dep.watch = watch
mydep.defined()
Dep.watch =null
}
}
else if(name ==='pushState' || name ==='replaceState'){
var method = history[name];
return function(){
method.apply(history, arguments);
mydep.notify()
}
}
}
}())
window.addHistoryListener = addHistoryMethod('historychange');
history.pushState = addHistoryMethod('pushState');
history.replaceState = addHistoryMethod('replaceState');
window.addHistoryListener('history',function(){
console.log('窗口的history改变了');
})
window.addHistoryListener('history',function(){
console.log('窗口的history改变了-我也听到了');
})
history.pushState({first:'first'}, "page2", "/first")
获得table里的行或列
var table = document.getElementById('mytable')
var tr = table.getElementsByTagName('tr')
js ascall码转换
str="A";
code = str.charCodeAt();//65
str2 = String.fromCharCode(code);//A
str3 = String.fromCharCode(0x60+26);//Z
添加或删除类名
添加:节点.classList.add("类名");
删除:节点.classList.remove("类名");
js中判断数组中是否包含某元素的方法
方法一:arr.indexOf(某元素):未找到则返回 -1。
arr.includes() 数组中含有某值返回true,没有返回false
event.x和event.clientX和event.offsetX
event.x和event.clientX为相对与窗口的位置。
event.offsetX为相对于目标对象的padding外边缘的位置,如果点击在border中的话,会取到负值
.concat()合并数组不会改变原数组,而是返回一个新数组
JS深拷贝
function deepCopy(obj) {
varresult = Array.isArray(obj) ? [] : {};
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
if(typeofobj[key] ==='object' && obj[key]!==null) {
result[key] = deepCopy(obj[key]); //递归复制
} else {
result[key] = obj[key];
}
}
}
return result;
}
js堆排序
// 交换两个节点
function swap(A, i, j) {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
}
// 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
// 假设 结点 i 以下的子堆已经是一个大顶堆,shiftDown函数实现的
// 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面
// 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点
// 都执行 shiftDown操作,所以就满足了结点 i 以下的子堆已经是一大
//顶堆
function shiftDown(A, i, length) {
let temp = A[i]; // 当前父节点
// j<length 的目的是对结点 i 以下的结点全部做顺序调整
for(let j = 2*i+1; j<length; j = 2*j+1) {
temp = A[i]; // 将 A[i] 取出,整个过程相当于找到 A[i] 应处于的位置
if(j+1 < length && A[j] < A[j+1]) {
j++; // 找到两个孩子中较大的一个,再与父节点比较
}
if(temp < A[j]) {
swap(A, i, j) // 如果父节点小于子节点:交换;否则跳出
i = j; // 交换后,temp 的下标变为 j
} else {
break;
}
}
}
// 堆排序
function heapSort(A) {
// 初始化大顶堆,从第一个非叶子结点开始
for(let i = Math.floor(A.length/2-1); i>=0; i--) {
shiftDown(A, i, A.length);
}
// 排序,每一次for循环找出一个当前最大值,数组长度减一
for(let i = Math.floor(A.length-1); i>0; i--) {
swap(A, 0, i); // 根节点与最后一个节点交换
shiftDown(A, 0, i); // 从根节点开始调整,并且最后一个结点已经为当
// 前最大值,不需要再参与比较,所以第三个参数
// 为 i,即比较到最后一个结点前一个即可
}
}
let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2];
heapSort(Arr);
alert(Arr);
希尔排序(增量排序)
//shellSort
function shellSort(arr) {
for(let gap = Math.floor(arr.length/2); gap > 0; gap = Math.floor(gap/2)) {
// 内层循环与插入排序的写法基本一致,只是每次移动的步长变为 gap
for(let i = gap; i < arr.length;
i++) {
let j =i;
let temp =arr[j];
for(; j> 0; j -=gap) {
if(temp >= arr[j-gap]) {
break;
}
arr[j]= arr[j-gap];
}
arr[j]=temp;
}
}
return arr;
}
// example
let arr = [2,5,10,7,10,32,90,9,11,1,0,10];
alert(shellSort(arr));
判断dom节点的是否包含某个子节点
contains 自带方法,判断一个元是不是另一个元的子集
document.documentElement.contains(document.body)
数组深拷贝
1.var arr = arr2.concat()
2.var arr = arr2.map(item=>return item)
JS-设计模式
工厂模式
工厂函数就是做一个对象创建的封装,并将创建的对象return出去
function newObj(name,age){
var o = new Object();
o.name=name;
o.age=age;
returno;
}
var obj = newObj();
单例模式:只允许存在一个实例的模式
var Instance = (function(){
varobj;
return function(){
if(obj === undefined) obj = new Date();
returnobj;
}
})();
var ibs = Instance();
观察者模式
又称发布订阅者模式,经典案例:事件监听,一个元素同时监听多个同类型事件,元素对象即为发布者,每一个事件处理函数即为订阅者。
策略模式
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,从而避免很多if语句,曾经学过最简单的策略模式雏形就是使用数组的方式解决传入数字得到对应星期几问题的算法。
比如公司的年终奖是根据员工的工资和绩效来考核的,绩效为A的人,年终奖为工资的4倍,绩效为B的人,年终奖为工资的3倍,绩效为C的人,年终奖为工资的2倍
var obj ={
"A": function(salary) {
return salary * 4;
},
"B" : function(salary) {
return salary * 3;
},
"C" : function(salary) {
return salary * 2;
}
};
var calculateBouns =function(level,salary){
returnobj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000
代理模式
代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问,为了不暴露执行对象的部分代码
//三个对象
//用户委托快捷方式打开exe
//为了不暴露执行对象的部分代码
//男孩委托快递小哥给女孩送礼物
var girl = function(name){
this.name =name;
}
//隐藏复杂,不愿意修改的的方法
var boy = function(girl){
this.girl =girl;
this.send = function(gift){
alert("你好:"+this.girl.name+",给你一个"+gift);
}
}
var proxyBro = function(girl){
this.send = function(gift){
new boy(girl).send(gift);
}
}
var pro = new proxyBro(new girl("Lisa"));
pro.send("么么哒");
pro.send("玫瑰花");
模块模式:
在立即执行函数表达式中定义的变量和方法在外界是访问不到的,只能通过其向外部提供的接口,"有限制"地访问.通过函数作用域解决了属性和方法的封装问题.
var Person = (function(){
var name = "xin";
var age = 22;
function getName(){
return name;
}
function getAge(){
return age;
}
return {
getName: getName,
getAge: getAge
}
})();
console.log(age); // 报错:age未定义
console.log(name); // 报错:name未定义
console.log(Person.age); // undefined
console.log(Person.name); // undefined
// 只能通过Person提供的接口访问相应的变量
console.log(Person.getName()); // xin
console.log(Person.getAge()); // 22
构造函数模式
混合模式
function Person(name,age){
this.name = name;
this.age = age;
};
Person.prototype.printName = function(){
console.log(this.name);
}
function Student(name,age){
// 继承 Person 的属性
Person.call(this,name,age);
}
function create(prototype){
function F(){};
F.prototype = prototype;
return new F();
}
// 让Student的原型指向一个对象,该对象的原型指向了Person.prototype,通过这种方式继承 Person 的方法
Student.prototype = create(Person.prototype);
Student.prototype.printAge = function(){
console.log(this.age);
}
var student = new Student('xin',22);
student.printName(); // "xin"
进制转换
parseInt(num,8); //八进制转十进制
parseInt(num,16); //十六进制转十进制
parseInt(num).toString(8)//十进制转八进制
parseInt(num).toString(16)//十进制转十六进制
parseInt(num,2).toString(8)//二进制转八进制
parseInt(num,2).toString(16)//二进制转十六进制
parseInt(num,8).toString(2)//八进制转二进制
parseInt(num,8).toString(16)//八进制转十六进制
parseInt(num,16).toString(2)//十六进制转二进制
parseInt(num,16).toString(8)//十六进制转八进制