js进阶学习一

97 阅读13分钟

js组成

stateDiagram-v2
JavaScript --> ECMAScript(js语言基础)
JavaScript --> webAPIs
webAPIs --> DOM(页面文档对象模型)
webAPIs --> BOM(浏览器对象模型)

js权威网站https://developer.mozilla.org/zh-CN/

直接搜索‘mdn’

DOM:是浏览器提供的用来操作网页内容的功能

DOM树是什么?

  • 将 HTML文档 以树状结构直观的表现出来,我们称之为 文档树 或 DOM 树
  • 描述网页内容关系的名词
  • 作用:文档树直观的体现了标签与标签之间的关系 image.png

DOM对象

  • 浏览器根据html标签生成的js对象
  • 所有的标签属性都可以在这个对象上面找到
  • 修改这个对象的属性会自动映射到标签身上 image.png

自定义属性

  • 在html5中推出来了专门的data-自定义属性
  • 在标签上一律以data-开头
  • 在DOM对象上一律以dataset对象的方式获取
<div class='box' data-id='1' data-value='22'>自定义属性盒子</div>

const box = document.querySelector('.box');
console.log(box.dataset) // 打印DOM集合 {id: 1, value: 22}

键盘监听

input.addEventListener('keyup', function (e) {
    if (e.key === 'Enter') {
        console.log('按下了回车键');
    }
})

事件委托

  • 是利用事件流的特征解决一些开发需求的知识技巧
  • 优点:减少注册次数,可以提高程序性能
  • 原理:利用事件冒泡的特点
    • 父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件。
  • 实现:事件对象.target.tagName可以获得真正触发事件的元素。

作用域

  • 局部作用域:包括函数作用域块作用域
    • 块作用域:在javascript中使用{}包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。

    1、let声明的变量、const声明的常量 都会产生块作用域,var不会产生块作用域。

    2、不同代码块之间互不影响

  • 全局作用域:<script>标签.js文件 的【最外层】就是所谓的全局作用域

注意\color{red}{注意}:应该尽可能少的声明全局变量,防止全局变量被污染。

作用域链:本质上是底层的变量查找机制

  • 在代码被执行时,会优先查看当前作用域中查找变量
  • 如果当前作用域查找不到,则会依次逐级查找父级作用域直到全局作用域。

总结\color{red}{总结}

1、嵌套关系的作用域串联起来就形成了作用域链

2、相同作用域链中按照从小到大的规则查找变量

3、子作用域能够访问父作用域,父作用域无法访问子作用域。

垃圾回收机制

  • 内存的生命周期:js环境中分配的内存,一般有如下生命周期
    • 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。
    • 内存使用:即读写内存,也就是使用变量、函数等。
    • 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存。
  • 说明
    • 全局变量一般不会回收(关闭页面回收)
    • 一般情况下局部变量的值不用了,会被自动回收
  • 内存泄漏:指程序中分配的内存由于某种原因,程序未释放无法释放
  • 算法说明:堆栈空间分配区别
    • 栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
    • 堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
    • 两种常见的浏览器垃圾回收算法引用计数法标记清除法

    1、引用计数

    IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象。

    • 算法:

      1)跟踪记录被引用的次数

      2)如果被引用了一次,那么就记录次数1,多次引用会累加++

      3)如果减少一个引用就减1 --

      4)如果引用次数是0,则释放内存

    • 缺点:嵌套引用(循环引用)

      如果两个对象相互引用,尽管他们已不再使用,垃圾回收器也不会进行回收,导致内存泄漏。

      function fn() {
        let o1 = {};
        let o2 = {};
        o1.a = o2;
        o2.a = o1;
        return '引用计数无法回收'; // 因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在,就会导致大量的内存泄漏
      }
      fn();
      

    2、标记清除法

    现代的浏览器已经不再使用引用计数算法了,现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

    • 核心:

      1)标记清除算法将“不再使用的对象”定义为“无法达到的对象

      2)就是从根部(在js中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。

      3)那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收

标记所有的引用

image.png

闭包

  • 概念:一个函数对周围状态的引用捆绑在一起,内存函数中访问到其外层函数的作用域。即【闭包 = 内层函数 + 外层函数的变量
  • 作用:封闭数据,提供操作,外部也可以访问函数内部的变量
  • 应用:实现数据的私有
function outer() {
    const a = 1;
    function f() {
        console.log(a)
    }
    f();
}

outer();

// 常见的闭包的形式,  外部可以访问或者使用 函数内部的变量
function outer() {
    let a = 10;
    function f() {
        console.log(a)
    }
    return f
}

// outer() 等价于 f => 等价于 function f() {}
const fun = outer();

fun();

创建对象的三种方式

  • 利用对象字面量创建对象
const o = { name: '章三' }
  • 利用new Object创建对象
const obj = new Object();
obj.name = '章三';

const obj1 = new Object({ name: '章三' })
  • 利用构造函数创建对象
    • 构造函数:是一种特殊的函数,主要用来初始化对象。
    • 使用场景:常规的 {...}语法允许创建一个对象。比如:我们创建了对象A,继续创建对象B 还需要重新写一遍,此时,可以通过构造函数快速创建多个类似的对象
// 创建对象A
const A = { name: '章三', age: 18 }

// 创建对象B
const B = { name: '里斯本', age: 20 }


=====================================

// 构造函数快速创建
function ComObj (name, age) {
    this.name = name;
    this.age = age;
}
// 创建对象A
const A = new ComObj('章三', 18);
// 创建对象B
const B = new ComObj('里斯本', 20);

构造函数在技术上是常规函数。

不过有两个约定:

  • 命名以大写字母开头

  • 只能由new操作符来执行

说明

  • 使用new关键字调用函数的行为被称为实例化
  • 实例化构造函数时,没有参数时可以省略(),但是不提倡省略
  • 构造函数内部无需写return,返回值即为新创建的对象
  • 构造函数内部的return,返回的值无效,所以不要写return
  • new Object()new Date()也是实例化构造函数

实例化执行过程(new的过程发生了什么?)

  • 创建一个新的空对象
  • 构造函数的this指向新对象
  • 执行构造函数代码,修改this,添加新的属性
  • 返回新对象

实例成员

通过构造函数创建的对象称为实例对象实例对象中的属性和方法称为实例成员(实例属性和实例方法)

说明:

  • 为构造函数传入参数,创建结构相同值不同的对象
  • 构造函数创建的实例对象彼此独立互不影响
function Person() {
    // 构造函数内部的 this 就是 实例对象
    
    // 实例对象中动态添加属性
    this.name = '章三';
    // 实例对象动态添加方法
    this.say = function () {
        console.log('hello~')
    }
}

// 实例化, p1 是实例对象
// p1 实际就是 构造函数内部的 this
const p1 = new Person();
console.log(p1);
console.log(p1.name) // 访问实例属性
p1.say() // 调用实例方法

静态成员

构造函数的属性和方法被称为静态成员(静态属性和静态方法)

说明:

  • 静态成员只能构造函数来访问
  • 静态方法中的this指向构造函数

比如:Date.now()Math.PIMath.random()

常用的静态方法:

  • Object.assign: 常用于对象拷贝。经常使用的场景:给对象添加属性
const o = { name: '章三', age: 16 }
Object.assign(o, { gender: '女' })
console.log(o) // { name: '章三', age: 16, gender: '女' }
function Person(name, age) {
    // 省略实例成员
}

// 静态属性
Person.eyes = 2;
Person.arms = 2;
// 静态方法
Person.walk = function () {
    console.log('hello~')
    // this 指向 Person
    console.log(this.eyes)
}

编程思想

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
  • 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。即:以对象功能来划分问题,而不是步骤

    面向对象编程(oop)

    • 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工
    • 面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目
    • 面向对象的特性: 1)封装性 2)继承性 3)多态性

    构造函数

    • 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现封装。
    • 同样的将变量和函数组合到一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。(存在浪费内存的问题
面向过程编程面向对象编程
优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如:单片机就采用的面向过程编程优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点:没有面向对象易维护、易复用、易扩展缺点:性能比面向过程低

原型

  • 目标:利用原型对象实现方法共享
  • 构造函数通过原型分配的函数是所有对象所共享的
  • Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象
  • 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
  • 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
  • 构造函数和原型对象中的this都指向实例化的对象image.png
  • constructor 属性

在哪里?

每个原型对象里面都有个constructor属性(constructor构造函数)

作用

该属性指向该原型对象的构造函数 image.png

Star === Star.prototype.constructor // true

使用场景

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。

但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor就不再指向当前构造函数了。

此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数。 image.png

  • 对象原型

对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。 image.png 注意\color{red}{注意}

  • __proto__是js非标准属性
  • [[prototype]]__proto__意义相同
  • 用来表明当前实例对象指向哪个原型对象的prototype
  • __proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数
function Star() {}
const s = new Star()

// 对象原型 __proto__ 指向 该构造函数的原型对象
console.log(s.__proto__ === Star.prototype) // true

// 对象原型 里面有 constructor 指向构造函数 Star
console.log(s.__proto__.constructor === Star) // true
  • 原型继承

继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,javascript中大多是借助原型对象实现继承的特性

  • 原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链 image.png 查找规则

  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  • 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象
  • 如果还没有就查找原型对象的原型(Object的原型对象
  • 依此类推一直找到Object为止(null
  • __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
  • 可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

this

  • this指向
    • 普通函数:调用方式决定了this的值,即谁调用 this 的值 指向谁

    普通函数没有明确调用者时,this值为window,严格模式下没有调用者时this的值为undefined

    • 箭头函数:事实上箭头函数中并不存在this
    • 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的
    • 箭头函数中的this引用的就是最近作用域中的this
    • 向外层作用域中,一层一层查找this,直到有this的定义

    ⚠注意\color{red}{⚠注意}

    • 在开发中【使用箭头函数前需要考虑函数中的this的值】,事件回调函数使用箭头函数时,this为全局的window。因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数。
    // DOM节点
    const btn = document.querySelector('.btn');
    
    // 箭头函数,此时 this 指向了 window
    btn.addEventListener('click', () => {
        console.log(this)
    })
    
    // 普通函数,此时 this 指向了 DOM对象
    btn.addEventListener('click', function () {
       console.log(this)
    })
    
    • 基于原型的面向对象也不推荐采用箭头函数
    function Person() {}
    
    // 原型对象上添加箭头函数
    Person.prototype.walk = () => {
        console.log(this) // window
    }
    const p1 = new Person();
    p1.walk();
    

    总结\color{red}{总结}

    • 箭头函数内不存在this,沿用上一级的
    • 不适用于:构造函数、原型函数、DOM事件函数等
  • 改变this
    • call():使用call方法调用函数,同时指定被调用函数中this的值

    语法: fun.call(thisArg, arg1, arg2, ...)

    • thisArg:在fun函数运行时指定的this
    • args1, arg2:传递的其他参数
    • 返回值就是函数的返回值,因为它就是调用函数
    • apply():使用apply方法调用函数,同时制定被调用函数中的this的值

    语法:fun.apply(thisArg, [argsArray])

    • thisArg:在fun函数运行时指定的this
    • argsArray:传递的值,必须包含在数组里面
    • 返回值就是函数的返回值,因为它就是调用函数
    • bind():不会调用函数,但是能改变函数内部this指向

    语法: fun.bind(thisArg, arg1, arg2, ...)

    • thisArg:在fun函数运行时指定的this
    • arg1, arg2:传递的其他参数
    • 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
    • 因此当我们只想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向