封面 pid: 80759766

闭包

闭包 = 函数 + 函数能够访问的自由变量

但从实践角度出发,闭包函数指:

  • 即使创建它的上下文已经销毁,它仍然存在
  • 在代码中引用了自由变量

直接点就是能够访问另一个函数作用域的变量的函数
为什么能够引用看似已经销毁的变量?因为函数上下文维护的作用域链,该变量能继续驻留在内存
如果不懂转此

原型、继承

你可以假设js里,万物皆对象

每个对象都有其原型,该原型也是一个对象。原型相当于一种共性的存在,可以理解为特定类型的所有实例共享的属性和方法。如果访问的对象属性不存在,那么他会沿着原型链往上寻找,找不到返回undefine

prototype是一个显式原型属性,只有(构造)函数拥有该属性,指向其原型对象;而原型对象中的constructor指向其对应的构造函数
__proto__是每个对象都有的隐式原型属性,指向了创建该对象的构造函数的原型

  • Object 是所有对象的爸爸,所有对象都可以通过 __proto__找到它
  • Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它
  • Function.prototypeObject.prototype 是两个特殊的对象,他们由引擎来创建
  • 除了以上两个特殊对象,其他对象都是通过构造器 new 出来的

继承的常用方法,括号缺点

  1. 原型链:子类原型指向父类实例(不能传参,单一继承,实例共享)
  2. 借用构造函数:子类中直接call父类(不继承父类原型属性)
  3. 组合:上两点结合使用(调用两次父类构造函数)
  4. 原型式:弄一个空函数F,其原型指向父类实例,返回F实例(没复用)
  5. 寄生组合:使用寄生单独继承父类原型属性,使用组合单独继承父类构造函数属性
    //寄生
    function wrap(obj){
    function Wrapper(){};
    Wrapper.prototype = obj;
    return new Wrapper();
    }
    let wrapper = wrap(Father.prototype);
    //组合
    function Son(){
    Father.call(this);
    }
    Son.prototype = wrapper;
    wrapper.constructor = Son;

事件循环

事件循环是js实现异步的一种方法,也是js的执行机制。

要维护的东西

JS在运行时会维护三样东西:

  • 函数栈:函数调用时一个帧(记录着参数和作用域链)会进栈,函数返回时该帧出栈
  • 堆:一片内存区域,对象被分配在堆中
  • 消息队列:包含待处理的消息及处理其的回调函数,他们按照FIFO的方式逐个处理

执行细节如下:

  1. JS在运行一段代码
    1. 遇到同步操作就立马执行
    2. 遇到异步操作就会为其注册函数并在完成的时候将返回的消息放进队列
    3. 事件的触发也会消息入队
    4. 执行期间函数栈会变动但最后函数栈会回归空。
  2. 函数栈归空后进入事件循环阶段,按照队列中的先后顺序处理消息(如果没有消息就再扫一遍),被处理的消息被移出队列并作为参数调用对应的回调函数。
  3. 因为2中回调函数的调用,重新进入1,这就是事件循环

消息的处理不会被中断,只有一个消息被完整的处理后才能轮到下一个消息。所以当一个消息处理时间过长时,后面的消息不得不等待。

let t1 = setTimeout(()=>{
  //假设这里的代码耗时10s
},1000)
let t2 = setTimeout((a)=>{
  console.log(a)
},2000,"23333")

上面的代码中,t1最先入队,t2比t1慢一秒入队,因此先处理t1,处理t1耗时十秒,然后才处理t2,打印出“2333”,并不是两秒后打印“2333”。所以setTimeout的第二个参数仅仅表示最少延迟时间,而非确切的等待时间。

另:Promise绝对优先于setTimeout/setInterval,可以理解为有两个优先级不一样的消息队列,优先处理Promise所属队列。

执行上下文

JS采用的是词法作用域,也就是静态作用域,函数的作用域在函数定义的时候就决定了。

var value = 1;
function foo() {
  console.log(value);
}
function bar() {
  var value = 2;
  foo();
}
bar(); //foo定义时外层的value是1,所以打印的就是1

JS中的可执行代码有:全局代码、函数代码、eval代码。JS引擎以他们为单位进行分析和执行,每执行一个函数,JS引擎就会创建一个函数上下文推入执行上下文栈,并在执行完成后弹出。程序开始时,全局上下文首先入栈作为栈底并开始分析执行;最后程序完成时,全局上下文出栈整个栈归空。

每个执行上下文,都有三个重要属性:

执行上下文的生命周期参考上方链接

DOM

位置与大小

  • offsetTop,offsetLeft,相对于最近一个position不为static的祖先的偏移
  • offsetHeight,offsetWidth,元素高宽,外含至边框
  • clientHeight,clientWidth外含至内边距
  • scrollHeight,scrollWidth,元素完整高宽(包括被隐藏待滚动部分),外含至内边距
  • scrollTop,scrollLeft,元素的滚动偏移
  • window.innerWidth,window.innerHeight,视口大小,含滚动条
  • Element.getBoundingClientRect(),返回对象,包含元素相对于视口的偏移和大小信息

API

node属性方法

Node.nodeName //返回节点名称,只读
Node.nodeValue //返回Text或Comment节点的文本值,只读
Node.textContent //返回当前节点和它的所有后代节点的文本内容,可读写
Node.baseURI //返回当前网页的绝对路径

Node.ownerDocument //返回当前节点所在的顶层文档对象,即document
Node.nextSibling
Node.previousSibling
Node.parentNode
Node.parentElement
Node.childNodes //返回当前节点的所有子节点
Node.children //返回指定节点的所有Element子节点
Node.first[Element]Child
Node.last[Element]Child
Node.childElementCount //返回当前节点所有Element子节点的数目。

//方法
Node.appendChild(node)
Node.hasChildNodes() //判断当前节点是否有子节点
Node.cloneNode(true); // 默认为false(克隆节点), true(克隆节点及其属性,以及后代)
Node.insertBefore(newNode,oldNode)  // 在指定子节点之前插入新的子节点
Node.removeChild(node) //删除节点,在要删除节点的父节点上操作
Node.replaceChild(newChild,oldChild) //替换节点
Node.contains(node) //判断参数节点是否为当前节点的后代节点。
Node.isEqualNode(noe) //判断两个节点是否相等(类型相同、属性相同、子节点相同)
Node.remove() //删除当前节点

document属性方法

document.activeElement //当前文档中获得焦点的那个元素。
document.URL
document.domain
document.location //location对象
document.referrer
document.title
document.characterSet
document.readyState //当前文档的状态
document.cookie

//方法
//querySelector和getElementBy*
//注册、移除、触发事件
document.createElement(tagName)
document.createTextNode(text)
document.createDocumentFragment() //生成DocumentFragment对象

element属性方法

Element.attributes //当前元素节点的所有属性节点
Element.id
Element.tagName
Element.innerHTML
Element.outerHTML //包含当前节点的innerHTMl
Element.className //当前元素的class属性
Element.classList //当前元素的class集合
Element.dataset //元素节点中所有的data-*属性。

Element.firstElementChild
Element.lastElementChild
Element.nextElementSibling
Element.previousElementSibling
Element.offsetParent //最近的position属性不为static的祖先元素

//方法
Element.getBoundingClientRect()
Element.getAttribute()
Element.setAttribute()
Element.hasAttribute()
Element.removeAttribute()

//querySelector和部分getElementBy*
//注册、移除、触发事件

优化

避免频繁操作dom,使用DocumentFragment处理操作并最后一次性插入;或者分批处理。避免大量注册事件,巧用事件委托

JS中有两种事件传播方式:事件捕获和事件冒泡(默认)。当事件触发时,会经历从根节点到事件源的捕获阶段(触发路径上的捕获型同类事件),接着触发事件源的事件,然后再经历从事件源到根节点的冒泡阶段(触发路径上冒泡型同类事件)。

20160213232257842.png

利用冒泡的特性,可以将大量子节点的事件委托给父节点处理,将要注册事件的个数减少为一。
addEventListener(type,listener,[useCapture=false])用于为元素的事件绑定回调函数,默认类型为冒泡型,事件被触发时执行回调函数,回调函数接受一个event对象,event.currentTarget代表当前被触发的元素,event.target代表事件源。在回调函数中调用event.stopPropagation()return false来阻止冒泡。

e.onclick=fun这类事件(DOM0事件)绑定不同,addEventListener可以为同一个事件(DOM2事件)绑定多个回调函数,而且可以通过removeEventListener手动取消绑定。

跨域

广义跨域:请求另一个域下的资源
狭义跨域:浏览器同源策略限制的一类请求场景

jsonp跨域

利用src不受同源策略的特性(因此只能get),通过script标签跟后端通信并告知回调函数名func,后端返回 func(...)来让前端接受数据

//回调函数
function func(res){
  console.log(res)
}
btn.onclick = function(){
  let s = document.createElement('script');
  s.src = 'http://localhost:3000/login?user=abc&pwd=123&callback=func';
  document.head.appendChild(s);
}
//后端返回func(data)让前端执行回调函数接收数据

domain + iframe跨域

同一主域下两个子域的通信,通过js强制设置document.domain为基础主域,就实现了同域,然后通过iframe进行通信

<!--a请求b -->
<!--a.dd.com/a页面 -->
<iframe id="iframe" src="http://b.dd.com/b" onload="func"></iframe>
<script>
    document.domain = 'dd.com';
    function func(){
        console.log(document.getElementById("iframe").contentWindow)
    }
</script>


<!--b.dd.com/b页面 -->
<script>
    document.domain = 'dd.com';
</script>

location.hash + iframe跨域

能实现不同域的通信,通过网址的hash来进行通信。
A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象(a可以开放回调函数来接受来自c的数据)

/*
|-----------------------|
|-----------------| A:a |
|----------|  B:b |     |
|   A:c    |      |     |
|          |      |     |
|----------|      |     |
|-----------------------|
*/
//a通过hash向b传数据,b监听来自a的hash并将响应通过hash传给c,ac同域干啥都行了

window.name + iframe跨域

能实现不同域的通信,通过window.name来进行通信。
A域下的a要从B域下的b获取数据,则a设置iframe指向b,b加载完后把数据写入window.name并呼a读取
实际使用时为了更安全,b写好window.name后会清空并让iframe指向空页面

postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递
  • 上面三个场景的跨域数据传递

postMessage(data,origin)方法接受两个参数,并触发目标窗口的onmessage事件

  • data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串
  • origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

    跨域资源共享(CORS)

    服务端设置Access-Control-Allow-Origin即可,若要带cookie请求:前后端都需要设置。
    这是非常主流的方法

还有 nginx代理跨域和websocket无视大法

防抖、节流

防抖:持续触发时,完全不执行;等最后一次触发结束的一段时间之后,再去执行

export function debounce(fun, wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function () {
            fun.apply(context,args)
        },wait);
    }
}

节流:持续触发并不会执行多次,到一定时间再去执行。在一段时间内,只执行一次

export function throttle(func, wait) {
    let context, args;
    let previous = 0;
    return function() {
        let now = +new Date();
        context = this;
        args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

浏览器内核(渲染进程)

浏览器进程是多线程的,具体包含

  • GUI渲染线程:解析html、css,构建render树,布局绘制
  • JS引擎线程:解析执行代码
  • 事件触发线程:负责控制事件循环,事件触发/异步完成后,推入消息队列
  • 定时触发器线程:负责setTimeout/setInterval计时,到时后推入消息队列
  • 异步HTTP请求线程:负责xhr,状态变更后,并且设置了回调函数的,推入消息队列

其中GUI渲染线程和JS引擎线程是互斥的,所有引起重渲染的dom操作将被缓存到任务队列中,等待JS线程的空闲时再激活渲染进程进行渲染。因此JS执行时间过长会阻塞页面的渲染

杂项

ES5中:this 永远指向最后调用它的那个对象(可以认为是调用点号前面的对象)
箭头函数的 this 始终指向函数定义时的this,而非执行时

new 的过程

  1. 创建一个空对象 obj
  2. 将新创建的空对象的隐式原型指向其构造函数原型
  3. 绑定 this
  4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。

typeof返回string、number、boolean、object、function、undefined,6者之一(ES6新增symbol)
js中基本类型(值类型):String、Number、Boolean、Null、Undefined、Symbol

js参数传递实际上传的都是一个副本,基础类型就不用说了,引用类型作为参数时传递的是变量的引用的副本

let obj = [0]
function change(obj){
  obj[0] = 1;//通过副本找到找到原值并修改
  obj = 2;//修改了副本,但是原引用还在
}
console.log(obj[0])//1

    添加新评论 | #

    Markdown Supported
    简单数学题:NaN + NaN =
    表情

    Comments | ?? 条评论

    • 单身为狗 22 年

    • 朝循以始 夜继以终

    • Blog: Von by Bersder

    • Alive: 0 天 0 小时 0 分

    Support:

    frame ssr server DNS music player

    © 2019-2021 ᛟᛊᚺᛁᚾᛟ

    back2top