基础深入
undefined 和 null 的区别
undefined 是定义了一个变量但没有赋值
null 是定义了一个变量并赋值为null
数据的类型
基本类型 –> String Number Boolean undefined null
引用类型 –> Object function array
变量的类型
值类型 –> 保存的是基本数据类型(值)
引用类型 –> 保存的是引用数据类型(地址值)
typeof
两个特殊的:null 和 arraytypeof 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