js知识总结—理论知识篇(一)

1. js 是由哪三部分构成?

  1. ECMAScript(JavaScript语法 )
  2. DOM 文档对象模型
  3. BOM 浏览器对象模型

ECMAScript

ECMAScript 是由ECMA 国际( 原欧洲计算机制造商协会)进行标准化的一门编程语言,这种语言在万维网上应用泛。ECMAScript 规定了JS的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套JS语法工业标准。

作用:提供JS核心语言功能

DOM —文档对象模型

文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标记语言的标准编程接口。 通过 DOM 提供的接口可以对页面上的各种元素进行操作(大小、位置、颜色等)。DOM 通过创建树来表示文档,从而使开发者对文档的内容和结构进行操作。

作用:提供访问操作网页内容的方法和接口。如果没有 DOM,就不能渲染页面和对页面经行操作了。

BOM —浏览器对象模型

浏览器对象模型(Browser Object Model),不同浏览器提供了可以对浏览器窗口进行访问和操作的方法。

例如:弹出新的浏览器窗口,移动、改变和关闭浏览器窗口,提供详细的网络浏览器信息(navigator object),详细的页面信息(location object),详细的用户屏幕分辨率的信息(screen object),对浏览器存储的的支持等等。

BOM作为JavaScript的一部分并没有相关标准的支持,每一个浏览器都有自己的实现,虽然有一些非事实的标准,但还是给开发者带来一定的麻烦。

作用:提供与浏览器交互的方法和接口。

2.如何理解 JS 中的this关键字?

“this” 一般是表示当前作用域所在的对象。在JS函数中的this关键字由函数的调用者决定,谁调用就this就指向哪个。如果找不到调用者,this将指向windows对象。

3.什么是闭包

什么是闭包:《js高级程序设计》对闭包的定义为,闭包,其实是一种语言特性,它是指的是程序设计语言中,允许将函数看作对象,然后能像在对象中的操作般在函数中定义实例(局部)变量,而这些变量能在函数中保存到函数的实例对象销毁为止,其它代码块能通过某种方式获取这些实例(局部)变量的值并进行应用扩展。

说白一点,闭包,并不是函数,但离不开函数。闭包是一种语言特性。什么特性呢?可以在函数中封装私有变量,私有变量不会污染全局。且私有变量能够通过其他方式(比如将函数作为返回值或者参数,也就是高级函数)让函数外部能够访问到其私有变量。而且,这个私有变量不会被回收。直到这个函数的实例被销毁或者手动销毁此变量。等于说变相延长了变量的生命周期。

如何形成闭包:当一个函数作用域中的变量被另一个函数引用的时候,就已经形成了闭包。

另一种解释:函数中嵌套函数,且外层函数局部变量被子函数访问。就会形成闭包。一个函数的返回值或参数是函数,也算是嵌套函数。

实际上,如果一个函数访问了它的外部变量,那么它就已经形成了闭包。

function add(n){
  var num = n
  return function addTo(x){
    return x + num
  }
}
addTwo = add(2)
addTwo(5)

内存:一个不能被回收的栈内存就是闭包。

js知识总结---理论知识篇(一)

作用:

  1. 封装私有变量或方法。在JS中没有明确的方法来创建私有方法,但是闭包可以。
  2. 外部想要访问函数内部变量,可以使用闭包。可以像访问全局变量一样去使用局部变量,延长了变量的生命周期。
  3. 可以创建不同的实例,且互不干扰。类似于面向对象。比如Vue组件中的deta使用函数和返回值定义数据。实际上就是工厂模式的对象。互不干扰。再比如Vue3和react中的hooks写法。

优点:

  1. 可以读取函数内部变量。
  2. 延长变量生命周期。
  3. 避免全局变量的污染

缺点:

  1. 闭包会常驻内存,增加内存消耗。
  2. 不恰当的闭包有可能会造成内存泄漏。

闭包有三个特性:

  1. 函数嵌套函数
  2. 函数内部引用了外部的参数和变量
  3. 参数和变量不会被垃圾回收机制回收

延伸:

  1. js作用域
  2. js作用域链
  3. js栈内存和堆内存
  4. js垃圾回收机制
  5. js中真的有堆和栈吗

4.js的作用域

作用域就是变量的使用范围,也就是在代码的哪些部分可以访问这个变量,哪些部分无法访问到这个变量,换句话说就是这个变量在程序的哪些区域可见。

作用域就是代码的执行环境,作用域存在于栈内存。

原生js(不含ES6)没有块级作用域只有局部作用域(函数内部)和全局作用域。

一般认为的作用域是词法作用域(也就是静态作用域,如函数的作用域在函数定义的时候就确定了),常见的有函数作用域(如果在函数内部给一个未定义的变量赋值,这个变量会转变为一个全局变量)和块作用域(块作用域把标识符限制在{}中)

作用域链:多个作用域对象连续引用形成的链式结构。一般情况下,变量在当前作用域中取值。但是如果在当前作用域中没有查到值,就会向上级(由下到上,由内而外)作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。注意:只有函数才能形成作用域。

ES6在有了let以后,拥有了块级作用域。但ES6的块级作用域并不是真正意义上的块级作用域。而在浏览器底层是用函数模拟的。也就是说,它的块级也是函数作用域(脚本作用域)。

//除函数外,普通的语句{}不会形成作用域,仍然属于全局。故如下代码可以打印if语句内申明的变量
var a = true;
if(a){var b = 222};
console.log(b); //222

//等同于如下,但建议用下面方式写,语义化更好
var a = true,b;
if(a){b = 222};
console.log(b); //222

函数会形成局部作用域。局部作用域内的变量申明不会污染外部作用域。

//函数和全局作用域
var a=100 //定义全局作用域变量
function fn(){
    var a=200  //在函数中定义变量不会污染外面定义的变量,只能函数里面用
    console.log('fn', a) 
}
console.log('1', a)  //1 100
fn() //fn 200

改变函数作用域的方法:

  1. eval()方法接收一个字符串作为参数,这个字符串的作用域与上下文同域。
  2. with关键字,是重复引用同一个对象的多个属性的快捷方式。

5.解释jsonp的原理,以及为什么不是真正的ajax?

原理:Jsonp就是利用浏览器可以动态地插入一段js并执行;

ajax的核心是 : 通过XMLHttpRequest(异步HTTP请求对象)获取非本页内容,

jsonp的核心 : 动态添加<script>标签来调用外部js脚本。

jsonp只支持get请求,ajax支持get和post请求

jsonp要用json格式传递数据,而ajax不一定用json格式,可能是字符串、数组等。

6.事件绑定和普通事件有什么区别?

事件绑定就是将事件通过类似于addEventListener的方法绑定在dom元素上;

普通事件即为dom元素直接调用事件方法;

普通事件可以被第二次掉用的事件覆盖,而事件绑定不会被覆盖,而是会依次执行。

DOM之事件详解参考以下文章:

http://code.zuifengyun.com/2021/03/2105.html

DOM之事件定义-Event类

7.js事件流(事件捕获、事件冒泡)

事件流:事件流是描述从页面中接收事件的顺序。也可以说是事件传播的顺序。

"DOM2事件流"规定的事件流包括三个阶段:

  1. 事件捕获阶段:提供了在事件没有送达目标之前查看事件的机会。与冒泡方向相反,由父向子逐级捕获。
  2. 处于目标阶段:目标对象(元素)本身的时间处理程序调用(执行)。
  3. 事件冒泡阶段。目标元素的事件大部分都会冒泡(传播)到DOM树根。有一些特殊的事件不会冒泡,比如mouseenter和mouseleave事件不会冒泡。若要冒泡,可以使用mouseover和mouseout替代。

addEventListener

事件处理程序的注册方法为addEventListener(),它接受3个参数(事件类型,事件回调函数,布尔值),其中最后一个参数默认值为false,将会把程序注册为冒泡事件处理程序。而其若为true,则为捕获事件处理程序。两者的区别是:若为冒泡,表示在冒泡阶段调用事件处理程序,逐渐冒泡到外层。而捕获恰好相反,在捕获阶段调用处理程序,执行顺序相反。在IE中只支持事件冒泡。移除事件使用removeEventListener(),传参如上。

IE8及以下浏览器不支持此方法,使用attachEvent(on+事件类型, 事件回调函数)方法,只有两个参数(不支持事件捕获)。移除事件使用detachEvent()方法,参数相同。

window.onclick

注册事件最简单的方法就是直接给元素加事件属性(DOM0级模型),将处理程序赋值给相应的事件属性(事件前面带on)。这种方式适用于所有浏览器。但是缺点是元素将最多只有一个事件处理程序。

如何实现先冒泡后捕获的效果

在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一事件,监听捕获和冒泡,分别对应不同的处理函数,监听到捕获事件,先暂缓执行其处理函数,直到冒泡事件被捕获后再执行捕获事件处理函数。

8.事件模型

js包含三种事件模型:DOM0事件模型(始事件模型),DOM2事件模型,IE事件模型

  1. DOM0级模型:事件不会流动传播。事件绑定监听函数方式:① html标签中添加属性如<div onclick="fun()"></div>;② js中获取dom元素后给元素加事件属性:document.onclick = fn,取消监听使用document.onclick = null
  2. IE事件模型:使用attachEvent("onclick", handler)detachEvent("onclick", handler)方法进行绑定事件和移除绑定。事件流只有2个阶段,没有事件捕获阶段。
  3. DOM2级模型:addEventListener()removeEventListener()进行监听和取消。事件流有3个阶段。

9.js事件委托

事件委托指的是,不在事件的目标对象(元素)上设置监听函数,而是在其父元素上设置监听函数。通过事件冒泡,父元素可以监听到子元素上事件的触发。通过判断事件目标元素的类型来做不同的操作。jQuery的on方法就是使用了事件委托。事件委托常用与动态元素的绑定,新添加的子元素也可以有监听函数,也可以触发事件回调。

举例:比如我们要实时监听li元素的事件,可以将事件注册到ul标签上。这样当li元素动态添加后也能捕获到添加的li元素事件。

10.js垃圾回收机制(GC)

js解释器当监测到一个对象(字符串、数组等)无用的时候,为了避免内存占用,就会把这一块的内存释放掉。这就是垃圾回收。GC执行时遍历所有对象,对不可访问的对象进行回收。

垃圾回收的方法是:

  1. 清除标记:垃圾回收器会给内存中的变量添加标记。当进入环境时标记“进入”,离开环境时标记“离开”。回收器会适时删除离开环境中的变量(当然被引用的变量不会被删除)。
  2. 引用计数:为每个引用类型的变量的引用次数计数,当被另一个变量引用时记为1,当引用其他变量时,这个变量计数-1,当回收器再次执行时就会清除计数为0的值。但如果相互引用的次数过多(循环引用),就不会触发回收器,造成内存泄漏。
  3. 若要回收一个变量,可以将变量手动赋值为null。

为什么需要垃圾回收?

由于字符串、对象、数组等数据没有固定的大小,只有当他们的大小已知时,才能对他们进行存储和分配。每次创建变量时js解释器都会分配内存来存储这个实体。如果内存不够用,将会造成内存溢出导致系统崩溃。所以必须要有一套内存的释放机制。这就是垃圾回收机制的必要性。

var a = "hello world"
var b = "hello"
var a = b //此时会释放掉"hello world"

11.js内存之堆和栈

堆(Heap)和栈(Stack)是程序内存的两种管理方式。其也是两种不同的数据结构。

栈内存:系统自动分配释放 ,用于存放基本类型的变量和对象的引用变量。栈内存存取速度比堆要快很多(栈使用一级缓存,堆使用二级缓存),仅次于寄存器(寄存器是cpu的内存单元)。满足先进后出的数据结构,栈数据可以共享。

堆内存:允许程序在运行时动态申请某个大小的内存空间。由开发人员分配和释放。结构类似于链表。相当于一棵树。

基本类型:String,Number,Boolean,Null,Undefined,symbol,这6种基本数据类型它们是直接按值存放的,所以可以直接访问。存于栈内存中。

引用类型:Function,Array,Object,当我们需要访问这三种引用类型的值时,首先得从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

函数被声明后在堆中以字符串形式存储,且申明函数作用域(脚本作用域)。被调用时在栈中开辟执行环境栈,作用是执行代码,而非保存代码。函数作用域(脚本作用域)中的变量在申明后会与全局区分开来,保障每个词法作用域的代码独立性。

内存溢出:

如果想要堆溢出,比较简单,可以循环创建对象或大的对象; 如果想要栈溢出,可以递归调用方法,这样随着栈深度的增加,JVM(虚拟机)维持着一条长长的方法调用轨迹(作用域链),直到内存不够分配,产生栈溢出。

相关知识点:深拷贝和浅拷贝

12.两个script标签中的代码,一处报错,另一处有影响吗?

两个script标签是两个代码块。报错不会影响另一处。但如果后面的代码块有引用前面的变量或对象,是会出错。

加载中...
加载中...