JavaScript使用总结

108 阅读15分钟

执行上下文 和 执行上下文栈

  1. 代码类型:分为全局代码和函数代码
  2. 执行上下文根据代码类型可以区分:全局执行上下文 和 函数执行上下文
    • 全局执行上下文:在执行全局代码前将window确定为全局执行上下文。过程:
      • 如果使用 var 定义的全局变量提升到全局前端,值为 undefined,添加为window的属性
      • function声明的的全局函数添加为window的方法
      • 给全局的 this 赋值 window
      • 执行全局代码
    • 函数执行上下文:只有在调用函数时才会生成函数执行上下文
      • 调用函数,在执行函数体之前,创建对应函数的函数执行上下文对象
      • 形参变量赋值实参传递的数据,添加为函数执行上下文的属性
      • argument被赋值所有实参的列表(一个类数组),添加为函数执行上下文的属性
      • 定义的变量添加为函数执行上下文的属性,使用 var 定义的变量还会提升到函数执行上下文的前端值为 undefined
      • function声明的的函数添加为函数执行上下文的方法
      • this赋值(调用函数的对象)
      • 执行函数执行上下文
  3. 执行上下文栈:在全局代码执行前,js引擎会创建一个用来储存管理所有执行上下文的栈
    • 在全局执行上下文(window)确定后,添加到栈中,也是第一个添加在栈中的执行上下文
    • 调用函数创建函数执行上下文,将函数执行上下文添加到栈中
    • 函数调用结束后,将在栈区中最顶层的函数执行上下文移除,依次从上到下将已经调用结束的函数的函数执行上下文,从栈区移除
    • 总结规律就是最后进入的函数执行上下文最先移除:后进先出
    • 当所有的代码执行结束后,栈中只剩下全局执行上下文(window)

变量提升 和 函数提升

  • 在代码中使用 var 定义的变量,会产生变量提升,也就是变量会提升到当前执行上下文的最前端。
  • 定义一个函数,函数也会进行提升,且在js中 是先提升变量,再提升函数。也就是函数一定在变量之前。
  • 如果函数定义使用的是 函数表达式:let fun = function(){},则函数不会提升

this的指向

this的指向是根据函数的调用方式(跟创建方式无关)来决定的。

  1. 以函数形式调用时,this 指向 window
  2. 以方法形式调用时,this 指向调用方法的对象
  3. 构造函数调用模式, 此时函数内部的 this 指向创建的 实例对象
  4. 函数的方法借调形式, 通过 apply、call、bind 等函数方法去传入对象, this 指向被传入的对象
    function fun() {
        console.log(this)
    }
    
    fun() // 函数形式调用 打印的this指向 window,前面的调用也可以是=> window.fun(), 也是一种方法调用
    let obj = {
        say: fun
    }
    obj.say() // 方法形式调用 打印的 this 指向 obj
    
    let obj2 = new fun() // 构造函数创建实例对象,this 指向当前创建的对象 obj2
    
    let obj3 = {}
    
    fun.apply(obj3, ...) // 这时候 this 指向 obj3,call、bind 方法也是一样

构造函数

构造函数和普通函数的区别就是调用方式不同, 普通函数是直接调用, 而构造函数是通过 new 关键字来调用, 构造函数的首字母一般为大写

构造函数的执行过程

  1. 创建一个新对象
  2. 将新建的对象设置为函数中的 this, 在构造函数中可以使用 this 来引用新建的对象
  3. 逐行执行构造函数中的代码
  4. 将新建的对象作为返回值返回

使用构造函数来创建对象可以很好的区分不同对象的类型

通过 instanceof 可以判断当前的对象是否是一个类(构造函数)的实例

但是所有的对象的顶级原型都是 Object, 也就是所有的对象都是 Object 的后代

注意点:

  • 在构造函数中, 不可以直接定义函数方法, 因为这样在创建多个对象时就会创建多个方法, 这样会消耗内存,可以在原型对象中去添加公共的方法
    // 形容狗的构造函数
    function Dog(name) {
        this.name = name
    }
    let dog1 = new dog()
    console.log(dog1) // 打印: Dog {}
    // 形容人的构造函数
    function Person(name) {
        this.name = name
        // 举例 向对象中添加方法
        // 这种方式去向对象中添加方法, ,每个对象中的方法都是唯一的且相同的, 
        this.say = function() {
        }
        // 1. 可以将函数定义在全局作用域中, 再将函数赋值给构造函数中的方法, 但不推荐这么做
        // 2. 原型: 
    }
    let person = new Person()
    console.log(person) // 打印: Person {}
    
    console.log(person instanceof Person) // 打印 true
    console.log(person instanceof Dog) // 打印 false
    console.log(person instanceof Object) // 打印 true

原型(prototype)与 原型链

创建一个函数, 函数中就会有一个属性 prototype, 函数的 prototype 对应着一个object空对象, 即 原型对象,简称原型

  • 原型对象中有一个属性 constructor,该属性指向函数对象,也就是其函数本身。
  • 如果函数作为普通函数调用则 prototype 没有作用
  • 给函数的原型对象添加属性(方法),通过该函数 new 实例对象时,该对象也拥有原型中添加的属性(方法)。
  • 如果函数以构造函数形式调用, 它所创建的 实例对象 中会有一个隐含的属性, 该属性指向构造函数的原型对象, 可以通过__proto__来访问该属性.
  • 所以用同一个构造函数创建的 实例对象 的 __proto__ 指向的原型对象都是同一个.
  • 相当于 原型对象 是一个公共区域, 由同一个构造函数(类)创建的 实例对象 都可以访问, 也就是可以将 对象共有的部分设置到 原型对象 中.
  • 原型链:
    • 当访问实例对象的一个属性时, 会先在对象的自身中去寻找这个属性, 如果存在则直接使用.
    • 如果不存在则会通过__proto__找到原型对象中是否存在, 存在则使用 (在编译器中会省略__proto__, 直接 对象.属性名, 这样访问也会执行上面的规则): 对象.__proto__.属性名 简写=> 对象.name.
    • 如果还不存在, 则会在原型对象的原型中去查找这个属性, 也就是: 对象.__proto__.__proto__.属性
    • 所有函数也是由 顶级函数 function Function(){} 创建的实例对象。也就是说 函数.__proto__ === Function.prototype
    • 而 顶级函数 function Function(){} 的 prototype 和__proto__ 是指向自身的原型对象:Function.prototype === Function.__proto__
    • 且 function Function(){} 的原型对象的隐式原型也指向 Object.prototype:Function.prototype === Object.prototype
    • 函数对象 function Object(){} 也是由顶级函数 function Function(){} 创建的实例对象,所以:Object.__proto__ === Function.prototype
    • 在 js 中顶级 函数对象 function Object(){},Object.prototype 也就是原型对象,是所有对象查找到的最高层原型对象,所以顶级函数 Function 的原型对象的隐式原型也指向Object.prototype:Function.prototype.__proto__ === Object.prototype, 而Object.prototype再往上的原型为 null:Object.prototype.__proto__ === null
    • 一直找到顶级 Object 的原型对象 Object.prototype,如果在Object的原型还没找到想要的属性, 则会返回 undefined

继承模式

  • 原型链继承
    • 一个构造函数想要继承另外一个构造函数的原型,则要 子类构造函数的原型指向父类的实例对象:子函数名.prototype = new 父函数名()
    • 上面的赋值,让子类可以获取到父类的属性或方法,但这时候访问子函数名.prototype.constructor属性时,指向的却是父类函数本身,这是因为 父类的实例对象赋值给了子类的原型,这里就需要及时改正:子函数名.prototype.constructor = 子函数名
    function Dog(name) {
        this.name = name
    }
    let dog = new Dog()
    
    //示例: 原型对象
    Dog.prototype.name = '哈哈哈'
    // 原型对象中 constructor 属性
    console.log(Dog.prototype.constructor === Dog) // 打印 true
    
    console.log(dog.__proto__ === Dog.prototype) // 打印 true
    console.log(dog.name) // 打印 '哈哈哈', 这里就是先查找自身是否存在 'name'属性,不存在则到原型对象中查找 => 相当于 dog.__proto__.name
    dog.name = '嘿嘿嘿' // 给dog对象添加了一个name属性,并赋值‘嘿嘿嘿’
    console.log(dog.name) // 打印 '嘿嘿嘿', 这里就直接打印自身存在的属性

闭包(Closure)

闭包:包含了被引用的变量或者函数的对象,存在于嵌套的内部函数中

  • 闭包产生的条件
    • 函数内部嵌套函数
    • 内部函数引用外部函数的变量或者函数
    • 执行外部函数(内部函数不执行也会产生闭包)
  • 常见闭包:
    • 将函数作为另外一个函数的返回值
    • 将函数作为实参传递给一个函数调用
  • 闭包的作用():
    • 使函数内部的变量在函数执行完之后,仍然存活在内存中,只有嵌套的内部函数引用的变量才可以,也就是存在闭包中的变量(延迟生命周期)
    • 让函数外部可以通过闭包操作到函数内部的数据(变量或函数)
    • 防止全局变量的污染
  • 闭包的缺点
    • 函数执行后,内部变量没有被释放,容易造成内存泄漏
    • 解决方法:用完及时主动释放

对象方法:

  • 对象.hasOwnProperty('属性名') // 查找当前对象自身是否存在这个属性

回收机制

JavaScript拥有自动的垃圾回收的机制, 会将垃圾对象从内存中销毁, 避免内存浪费导致运行卡顿.

所以我们在编写代码时, 存在不需要的对象(变量)时, 可以将对象(变量)的值设置为 null, 使得引用的对象得到释放.

函数的方法

call() apply() 这两个方法都是函数对象的方法, 需要通过函数对象来调用. 当函数调用这两个方法都会调用函数执行.

call() apply() 可以将一个对象指定为第一个参数传给函数内部, 这个对象会成为函数执行的 this , 也就是修改 this 的指向

当然在定义函数的时候会指定形参, 而 call 和 apply 在传入指定的 对象 时不会和形参出现冲突.

  • call(obj, 实参1, 实参2, ...) call 方法第一个实参用于传入指定对象, 对应实参依次传入
  • apply(obj, [实参1, 实参2, ...]) apply 方法第一个实参传入指定对象, 对应的实参组成数组作为第二个参数传入

函数中的方法:

  • arguments: 获取调用函数传入的所有实参, 是一个类数组, 但不是数组, 可以使用数组的方法.
  • arguments.length: 获取实参的长度, 也就是传入了多少个实参
  • arguments.callee: 获取到当前函数对象

正则表达式

通过构造函数 RegExp 创建一个正则对象: new RegExp("正则表达式规则", "匹配模式")

通过字面量创建正则表达式: let 变量 = /正则表达式/匹配模式

匹配模式: 该参数有两个值 i: 忽略大小写; g: 全局匹配模式(默认该值);

正则对象可以通过 test() 方法检查一个对象是否符合正则表达式的规则, 检查的方式是: 检查对象中是否存在指定的元素, 且校验默认严格区分大小写, 返回值为布尔值. 还有其他方法可以查询w3cschool

正则表达式 符号意义:

  • '|': 表示或, 用于隔开表达式字符. 例: /a|b/ 检查是否存在a 或者 b
  • '[字符]': 方括号, 查找方括号之间的任何字符。例: /[abc...]/ 检查是否存在a或者b或者c或者...
  • '[0-9]': 查找任何从 0 至 9 的数字。
  • '[a-z]': 查找任何小写的从 a 至 z 的字母
  • '[A-Z]': 查找任何大写的从 A 至 Z 的字母
  • '[A-z]': 查找任何大小写字母
  • '[^字符]': 除了该字符. 例:let reg = /[^a]/ reg.test('a')=>打印false; reg.test('ab')=>打印true
  • '^': 开头字符
  • '$': 结尾字符
    // 使用构造函数创建
    let reg = new RegExp("a")
    let str = 'a'
    console.log(reg.test(str)) // 打印 true, 检查的字符串包含'a'符合规则
    str = 'abc'
    console.log(reg.test(str)) // 打印 true, 检查的字符串包含'a'符合规则
    str = 'skdf'
    console.log(reg.test(str)) // 打印 false, 检查的字符串不包含'a'不符合规则
    
    //使用字面量创建, 与构造函数创建用法一致
    let reg2 = /a/g

DOM

浏览器加载一个页面(HTML)时,是按照自上到下的顺序加载的,所以最好的情况 script 标签会写在 body 标签中的最后面,也就是在加载页面结构后再执行 js 代码。当然也可以在 script 标签中添加事件(onload)监听,当页面加载完成之后再去执行 js 代码,这样 script 标签写在哪里都可以。

<script>
    window.onload = function() {
        // 编写 js 代码, 这里就可以在页面加载完成之后再执行
    }

</script>

DOM查询

也就是获取元素节点,通过 document 对象调用相应的方法,获取对应的元素节点

let dom = document.getElementById('btn') // 这里就是获取到页面中id属性的属性值为 'btn' 的元素
方法描述
getElementById()返回带有指定 ID 的元素。
getElementsByTagName()返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组(类数组))。
getElementsByClassName()返回包含带有指定类名的所有元素的节点列表。
getElementsByName()返回包含带有指定属性name的所有元素节点列表
appendChild()把新的子节点添加到指定节点。
removeChild()删除子节点。
replaceChild()替换子节点。
insertBefore()在指定的子节点前面插入新的子节点。
createAttribute()创建属性节点。
createElement()创建元素节点。
createTextNode()创建文本节点。
getAttribute()返回指定的属性值。
setAttribute()把指定属性设置或修改为指定的值。

document属性

  • body 属性 表示页面的 body 元素节点: document.body
  • documentElement 属性 表示页面的根标签 HTMl:document.documentElement
  • all 属性 表示页面中所有的元素节点列表

document方法

  • querySelector() 方法 可以根据css选择器的字符串作为参数来查询一个元素节点对象,当页面有多个相同的选择器元素,则只会找到第一个:document.querySelector('.box div')
  • querySelectorAll() 方法 可以根据css选择器的字符串作为参数来查询元素节点列表。

获取元素节点的子节点 通过具体的元素节点对象调用相应的方法获取子节点,也就是先通过上面 document对象的方法获取到元素节点对象,再调用方法获取子节点

  • getElementsByTagName()方法 返回当前节点下指定标签名的子节点列表
  • childNodes 属性 表示当前节点的所有子节点,不仅是元素节点,是包括文本节点(标签之间的空白也会识别为文本节点)等所有节点
  • children 属性 表示当前节点的所有 子元素节点,不包含文本节点
  • firstChild 属性 表示当前节点的第一个子节点(包括文本节点)
  • firstElementChild 属性 表示当前节点的第一个 子元素节点(不兼容IE8)
  • lastChild 属性 表示当前节点的最后一个子节点
  • parentNode 属性 表示当前节点的父节点
  • previousSibling 属性 表示当前节点的前一个兄弟节点(会获取空白的文本节点)
  • previousElementSibling 属性 表示当前节点的前一个兄弟元素节点
  • nextSibling 属性 表示当前节点的后一个兄弟节点,同理

DOM事件句柄 (Event Handlers)

属性此事件发生在何时...IEFOW3C
onabort图像的加载被中断。419Yes
onblur元素失去焦点。319Yes
onchange域的内容被改变。319Yes
onclick当用户点击某个对象时调用的事件句柄。319Yes
ondblclick当用户双击某个对象时调用的事件句柄。419Yes
onerror在加载文档或图像时发生错误。419Yes
onfocus元素获得焦点。319Yes
onkeydown某个键盘按键被按下。31NoYes
onkeypress某个键盘按键被按下并松开。319Yes
onkeyup某个键盘按键被松开。319Yes
onload一张页面或一幅图像完成加载。319Yes
onmousedown鼠标按钮被按下。419Yes
onmousemove鼠标被移动。319Yes
onmouseout鼠标从某元素移开。419Yes
onmouseover鼠标移到某元素之上。319Yes
onmouseup鼠标按键被松开。419Yes
onreset重置按钮被点击。419Yes
onresize窗口或框架被重新调整大小。419Yes
onselect文本被选中。319Yes
onsubmit确认按钮被点击。319Yes
onunload用户退出页面。319Yes

改变 HTML 样式 获取 HTML 样式

通过 HTML DOM,您能够访问 HTML 元素的样式对象。获取元素节点对象,通过style属性更改元素节点的样式,更改的样式属性与css一致,但css中以‘-’连接的样式名要修改成驼峰命名。而且 js 修改的样式,是通过行内样式修改的。所以css样式中如果存在‘!important’ 的样式 js无法修改。

<html>
<body>
    <p id="p2">Hello world!</p>
    <script>
        document.getElementById("p2").style.color="blue";
    </script>
</body>
</html>

获取 HTML 样式

获取指定元素的 CSS 样式。 window.getComputedStyle()

内存溢出 与 内存泄漏

  • 内存溢出:1.一种程序运行出现错误。2.程序运行需要的内存超过剩余内存时,就会抛出内存溢出的错误
  • 内存泄漏:占用的内存没有及时释放,内存泄漏积攒多了就容易内存溢出
    • 常见的内存泄漏:1.意外的全局变量。2. 没有及时清理的计时器和回调函数。3. 闭包