JavaScript高级笔记


基础深入

undefined 和 null 的区别

undefined 是定义了一个变量但没有赋值
null 是定义了一个变量并赋值为null

数据的类型

基本类型 –> String Number Boolean undefined null
引用类型 –> Object function array

变量的类型

值类型 –> 保存的是基本数据类型(值)
引用类型 –> 保存的是引用数据类型(地址值)

typeof

两个特殊的:null 和 array
typeof null = 'object'
typeof array = 'object'

其余的数据类型 typeof 都是返回它本身
typeof undefined = 'undefined'
typeof function = 'function'

对象

多个数据的封装体
组成:
属性(字符串 属性值是任意值)
方法(属性值是函数)

数组:arr[索引值]
对象:obj[‘属性名’] -> [ ]内可以接收变量

属性名包含特殊字符的时候 使用 obj[‘属性名’]

函数

什么是函数?
实现特定功能的 n 条语句的封装体
只有函数是可以执行的

为什么要用函数?
提高代码复用、便于阅读理解

定义函数

1.函数声明

function 函数名(形参){
  n 条语句
}

2.表达式

var fn = function(形参){
  n 条语句
}

执行函数
1.fn() -> 直接调用 (this:window)
2.obj.fn() -> 以方法形式调用 (this:obj)
3.new Fn() -> 通过new调用 (this:新创建的对象)
4.fn.call/apply(this) -> 指定函数 this 调用
如果不指定this则与直接调用没区别

call方法:
fn.call(指定函数fn的this,实参1,实参2,…)
apply方法:
fn.apply(指定函数fn的this,[实参1,实参2,…])

call指定实参是分开的
apply指定实参是用数组封装起来的

回调函数

call back

  • 我定义的
  • 我没有主动调用
  • 但最终调用了

立即执行函数

(function(形参){
  n 条语句
})()

将匿名函数用()括起来看作一个整体,然后末尾加上()表示调用

作用
()内的是匿名函数是局部作用域,不会污染外部(全局)命名空间

JS分号问题

可以不加分号(根据个人喜好)
但是又两个特殊的地方必须加
()前必须加
[]前必须加

var a = 3
;(function(){})() //立即执行函数

函数高级

原型 prototype

  • 每个函数对象都有一个prototype属性,它默认指向一个空Object对象(称为:原型对象)
  • 每个函数的原型对象都有一个constructor属性,它引用函数本身
  • 例:Person.prototype.constructor === Person
  • 函数创建的所有实例对象 都可以访问到原型对象中的属性或方法
  • 实例对象.constructor === 构造函数.prototype.constructor
  • [constructor出现在函数的原型对象prototype身上就是为了 让这个函数所创建的所有实例能 通过.constructor来获取构造函数]

显式原型、隐式原型

显式原型
每个函数都有一个 prototype 属性 即 显式原型

隐式原型
每个实例对象都有 一个 即 __ proto __ 隐式原型

原型对象其实也是一个实例对象 由 Object 创建的 所以也有__proto__这个属性
隐式原型对象 指向 缔造者(构造函数)的原型对象

关于原型的两个内部语句

function Fn(){
/*
  内部语句:this.prototype = new Object() 
  等同于:this.prototype = {}
  prototype对象中的 默认 添加constructor这个属性
*/
}

var fn = new Fn()
// 内部语句 this./__ proto /__ = Fn.prototype

原型链

每个函数都有 显式原型对象 和 隐式原型对象
显式原型对象 .prototype 是由 Object函数创建的
隐式原型对象 .__proto__ 指向这个函数 的 缔造者的 原型对象 Function.prototype

查找变量
变量通过作用域找不到 报错
通过对象.属性 查找一个变量找不到 返回undefined
实例对象.属性 通过原型链找不到这个属性 返回 undefined

  • Object 是一个函数
  • Function是一个函数

1.函数的显式原型指向的对象默认是 Object的实例对象(但Object除外)
原型链终点:Object.prototype.__proto__ === null Object的原型对象
2.任何函数都是new Function() 创建的(包括Function本身)
所以 任何函数的__proto__都是Function.prototype

Function.__proto__ === Function.prototype // true

实例对象的 隐式原型对象 等于 构造函数的 显示原型对象

原型链是 .__proto__.__proto__.__proto__ 连起来的一条链子

instanceof

表达式 A instanceof B
返回一个布尔值
如果函数B的显示原型prototype 在 A实例对象的原型链上,则返回true

变量声明提升

var
var 声明变量,会在所有代码执行之前声明(但没赋值)
声明了没赋值 undefined

/*
var 没有块级作用域的概念,所有的var变量提前声都是在 作用域的顶部声明。
注意:只有函数才能切割全局作用域
所以 var声明 只有两个情况:
1.在全局作用域的顶部声明
2.在局部作用域的顶部声明


Tips: if 没有切割作用域(只有函数才能切割全局作用域)
*/

ES6中:let 和 const 才有块级作用域的概念

函数声明提升

function
function 函数名(){} 会在所有代码执行之前创建,所以可以在函数声明前调用函数
var 函数名 = function(){} 声明了,但没赋值,不能再声明前调用函数

  • 函数提升 比 变量提升的优先级高,函数、变量同名,不会被变量声明覆盖,但是会被变量赋值之后覆盖

执行上下文

全局执行上下文

1.所有代码执行之前 将 window 确定为全局执行上下文

2.对全局数据进行预处理

  • var 定义的变量 ==> 赋值 undefined ,添加为window的属性
  • function 声明的全局函数 ==> 赋值 ,添加为window的方法
  • this赋值为window

3.开始执行全局代码

函数执行上下文

1.准备调用函数之前,创建对应的函数执行上下文对象

2.对局部数据进行预处理

  • 形参变量 赋值 实参,添加为执行上下文的属性
  • arguments 赋值 实参列表(伪数组),添加到执行上下文的属性
  • var 定义的变量 ==> 赋值 undefined ,添加为window的属性
  • function 声明的全局函数 ==> 赋值 ,添加为window的方法
  • this 赋值为 调用函数的对象

3.开始执行

4.函数执行完毕释放空间,清除执行上下文

栈(stack)的结构

后进先出
window压栈

作用域链

  • 每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了
  • 全局执行上下文是在全局作用域确定之后,JS 代码执行之前创建的
  • 关键:作用域是静态的,只要函数定义好了就一直是这样不会变化。
  • 执行上下文是动态的,调用函数时创建,函数执行完毕就会自动释放

闭包

如何产生?

当一个嵌套的内部函数引用了外部函数的变量时,就产生了闭包

闭包是什么?

理解一:闭包是嵌套的内部函数
理解二:在内部函数中 有一个 包含被引用变量的对象(closure)

我的详细理解

/*
父函数 里面嵌套 子函数
子函数 引用了 父函数的数据
父函数 将 子函数返回return,一个变量接收父函数的返回值
通过这个变量可以调用子函数,但是调用子函数 必须用到 父函数的数据
父函数 return 之后 代表已经执行完毕。
正常情况:一个函数执行完毕会释放内存空间,清除执行上下文。
关键是 子函数 还要用到 父函数的数据 所以 父函数 return之后 数据还存在于内存中
没有被清除,这就形成了闭包
*/

产生条件

  • 函数嵌套
  • 内部函数引用了外部函数的数据
  • Tips:不用调用内部函数就已经产生了闭包

闭包次数问题

外部函数被调用了几次就产生了几个闭包

常见的闭包

1.将内部函数作为返回值返回
2.内部函数作为实参传递给另一函数调用

闭包的作用

1.使函数内部的变量在函数执行完毕之后,仍然存活在内存中
2.让函数外部可以操作到函数内部的数据

闭包的生命周期

产生:内部嵌套的函数 定义执行完毕时就产生了
死亡:内部嵌套的函数 称为垃圾对象时(垃圾对象会被浏览器垃圾回收机制清除)

闭包的应用

定义JS模块

  • 将所有数据和功能都封装在一个函数内部(私有化)
  • 向外暴露一个包含 n 个方法的对象或函数
  • 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

闭包的缺点

1.函数执行完毕之后,函数内的局部变量没有释放,占用内存
2.容易造成内存泄露
解决:能不用闭包就不用,及时释放(= null)

内存溢出、泄露

内存溢出
程序运行占用内存过大,导致溢出,会报错

内存泄露

  • 占用的内存没有及时释放
  • 泄露过多久会导致内存溢出
    常见的内存泄露:
    1.意外的全局变量
    2.没有及时清理定时器 或 回调函数
    3.闭包

对象高级

对象的创建模式

Object 构造函数

  • 先创建Object对象,再动态添加属性和方法
  • 适用场景:起始不确定对象的内部数据
  • 缺点:语句太多

对象字面量

  • 使用{}创建对象,同时添加属性和方法
  • 适用场景:创建前就知道对象的内部数据
  • 缺点:如果创建多个对象,有重复代码

工厂模式

  • 定义一个函数,这个函数每次return对象
  • 适用场景:需要创建多个对象
  • 缺点:对象没有确切的类型,多个对象区别不开

自定义构造函数

  • 自定义构造函数,通过 new 创建对象
  • 适用场景:需要创建多个类型确定的对象
  • 缺点:每个对象都有相同的数据(方法),浪费内存

构造函数与原型组合

  • 自定义构造函数,属性在 new 的时候初始化,方法添加到原型身上
  • 适用场景:需要创建多个类型确定的对象时

原型链继承

子类型的原型对象 为 父类型 的 实例对象
使 子类型的原型对象.__proto__ === 父类型.prototype
原本 子类型的原型对象.__proto__ === Object.prototype

子类型创建的实例对象 的 原型链上就可以找到 父类型的原型对象
可以调用夫类型原型对象身上的属性或方法

/*
A是父类型构造函数 实例对象 a
B是父类型构造函数 实例对象 b

b.__proto__ === B.protoype
B.prototype.__proto__ === Object.prototype

` <!-- 关键: 使子类型的原型对象 为 父类型的 实例对象 -->`
B.prototype = new A()
`<!-- 修正constructor属性,要不然就找到父类型的原型对象.constructor -->`
B.prototype.constructor = B 

`<!-- 这时 B.prototype 就是 A的 实例对象-->`
B.prototype.__proto__ === A.prototype

b 就可以通过原型链找到 A.prototype ,能找到就能使用 A 原型身上的属性或方法
但 b 是 new B() 创建的

这就让 B 继承了 A
*/

线程机制

这里没弄太明白

进程和线程

多进程:一个应用程序可以同时启动多个实例运行
多线程:在一个进程内,同时由多个线程运行

  • JS 是单线程运行的
  • 浏览器是多线程运行的

分线程

var worker = new Worker('worker.js')

  • 不会阻塞主线程

不常用原因:
1.慢
2.不能跨域加载JS
3.worker 内代码不能访问 DOM 更新页面,因为全局作用域看不到window和document
4.不是每个浏览器都兼容

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/272907.html

(0)
上一篇 2022年7月9日
下一篇 2022年7月9日

相关推荐

发表回复

登录后才能评论