js基础

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)//十六进制转八进制

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容