JS 高级

JS 高级

基础总结深入

数据类型

分类

  • 基本(值)类型
    • String:任意字符串
    • Number:任意的数字
    • Boolean:true false
    • Undefined:undefined
    • Null:null
  • 对象(引用)类型
    • Object:任意对象
    • Function:一种特别对象(可以执行)
    • Array:一种特别的对象(数值下标,内部数据有序)

判断

  • ===: 判等判断

    可以判断:undefined, null

  • typeof: 返回数据类型的字符串表达

    可以判断:undefined / number / string / true/false / function

    不能判断:null与object object与array

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a
    console.log(a, typeof a, typeof a==='undefined',a===undefined ) // undefined 'undefined' // true true
    console.log(undefined==='undefined') //false
    a = 4
    console.log(typeof a==='number')
    a = 'atguigu'
    console.log(typeof a==='string')
    a = true
    console.log(typeof a==='boolean')
    a = null
    console.log(typeof a, a===null) // 'object'
  • instanceof:判断对象的具体类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     var b1 = {
    b2: [1, 'abc', console.log],
    b3: function () {
    console.log('b3')
    return function () {
    return 'xfzhang'
    }
    }
    }

    console.log(b1 instanceof Object, b1 instanceof Array) // true false
    console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
    console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true

    console.log(typeof b1.b2) // 'object' typeof数组返回'object'
    console.log(typeof b1.b3==='function') // true

    b1.b2[2](4)
    console.log(b1.b3()())

几个问题

  • undefined 与 null 的区别

    undefined代表定义未赋值

    null定义并赋值了, 只是值为null

  • 什么时候给变量赋值为null呢?

    初始赋值, 表明将要赋值为对象

    结束前, 让对象成为垃圾对象(被垃圾回收器回收)

    1
    2
    3
    4
    5
    6
    //起始
    var b = null // 初始赋值为null, 表明将要赋值为对象
    //确定对象就赋值
    b = ['atguigu', 12]
    //最后
    b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收)
  • 严格区别变量类型与数据类型

    数据类型:基本类型/对象类型

    变量类型:

    基本类型:保存就是基本类型的数据

    引用类型:保存的是地址值

数据变量内存

  • 关于引用变量赋值问题

    2个引用变量指向同一个对象, 通过一个变量修改对象内部数据, 另一个变量看到的是修改之后的数据

1
2
3
4
5
6
7
8
9
var obj1 = {name: 'Tom'}
var obj2 = obj1
obj2.age = 12
console.log(obj1.age) // 12
function fn (obj) {
obj.name = 'A'
}
fn(obj1)
console.log(obj2.name) //A

2个引用变量指向同一个对象, 让其中一个引用变量指向另一个对象, 另一引用变量依然指向前一个对象

1
2
3
4
5
6
7
8
9
10
11
12
var a = {age: 12}
var b = a
a = {name: 'BOB', age: 13}
b.age = 14
console.log(b.age, a.name, a.age) // 14 Bob 13

function fn2 (obj) {
obj = {age: 15}
}
fn2(a)

console.log(a.age) // 13
  • 对象释放内存

​ 局部变量: 函数执行完自动释放

        对象: 成为垃圾对象==>垃圾回收器回收

对象

对象的组成

 属性: 属性名(字符串)和属性值(任意)组成

​ 方法: 一种特别的属性(属性值是函数)

访问对象内部数据

.属性名: 编码简单, 有时不能用

[‘属性名’]: 编码麻烦, 能通用

  • *必须使用[‘属性名’]的方式

    1. 属性名包含特殊字符: - , 空格

    2. 属性名不确定

1
2
3
4
5
6
7
8
9
10
11
12
13
var p = {}
//1. 给p对象添加一个属性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])

//2. 属性名不确定
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value
console.log(p[propName])

函数

定义函数

1
2
3
4
5
6
function fn1 () { //函数声明
console.log('fn1()')
}
var fn2 = function () { //表达式
console.log('fn2()')
}

函数调用

  1. test(): 直接调用

  2. obj.test(): 通过对象调用

  3. new test(): new调用

  4. test.call/apply(obj): 临时让test成为obj的方法进行调用

1
2
3
4
5
6
7
8
var obj = {}
function test2 () {
this.xxx = 'atguigu'
}

// obj.test2() 不能直接, 根本就没有
test2.call(obj) // obj.test2() // 可以让一个函数成为指定任意对象的方法进行调用
console.log(obj.xxx)

回调函数

  1. 什么函数才是回调函数?

    1. 你定义的
    2. 你没有调
    3. 但最终它执行了(在某个时刻或某个条件下)
  2. 常见的回调函数?

    • dom事件回调函数 ==>发生事件的dom元素

    • 定时器回调函数 ===>window

    • ajax请求回调函数

    • 生命周期回调函数

IIFE

全称: Immediately-Invoked Function Expression

作用

  • 隐藏实现
  • 不会污染外部(全局)命名空间
  • 用它来编码js模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(function () { //匿名函数自调用
var a = 3
console.log(a + 3)
})()
var a = 4
console.log(a)


;(function () {
var a = 1
function test () {
console.log(++a)
}
window.$ = function () { // 向外暴露一个全局函数
return {
test: test
}
}
})()

$().test() // 1. $是一个函数 2. $执行后返回的是一个对象

函数中的this

  1. this是什么?
    • 任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window
    • 所有函数内部都有一个变量this
    • 它的值是调用函数的当前对象
  2. 如何确定this的值?
    • test(): window
    • p.test(): p
    • new test(): 新创建的对象
    • test.call(obj): obj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Person(color) {
console.log(this)
this.color = color;
this.getColor = function () {
console.log(this)
return this.color;
};
this.setColor = function (color) {
console.log(this)
this.color = color;
};
}

Person("red"); //this是谁? window

var p = new Person("yello"); //this是谁? p

p.getColor(); //this是谁? p

var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj

var test = p.setColor;
test(); //this是谁? window

function fun1() {
function fun2() {
console.log(this);
}

fun2(); //this是谁? window
}
fun1();

函数高级

原型和原型链

原型prototype

函数的prototype属性

  • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  • 这个原型对象中有一个属性constructor, 它指回这个函数对象

给(一般是构造函数的)原型对象添加属性(一般都是方法)

  • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
console.log(Date.prototype, typeof Date.prototype) //object 'object'
function Fun () {//alt + shift +r(重命名rename)

}
console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性)

// 原型对象中有一个属性constructor, 它指向函数对象
console.log(Date.prototype.constructor===Date)
console.log(Fun.prototype.constructor===Fun)

//给原型对象添加属性(一般是方法) ===>实例对象可以访问
Fun.prototype.test = function () {
console.log('test()')
}
var fun = new Fun()
fun.test()

显式原型和隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 总结:
    • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
    • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
    • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
1
2
3
4
5
6
7
8
9
10
11
12
//1.定义构造函数
function Fn() { // 内部语句: this.prototype = {}

}
//2.创建实例对象
var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
//3.给原型添加方法
Fn.prototype.test = function () {
console.log('test()')
}
//4.通过实例调用原型的方法
fn.test()

原型链

  • 访问一个对象的属性时,
    • 先在自身属性中查找,找到返回
    • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
    • 如果最终没找到, 返回undefined
  • 别名: 隐式原型链
  • 作用: 查找对象的属性(方法)

深入理解原型

1.

1
2
var o1 = {}
var o1 = new Object()

​ 直接创建对象时,obj这个对象本质上是被Object函数创建的,因此obj.__proto__=== Object.prototype,即,每个对象都有一个proto属性,指向创建该对象的函数的prototype

  1. ```js
    var f1 = new Foo()

    1
    2
    3
    4
    5
    6
    7
    8

    {% asset_img 1780825-20190829182201479-1238403745.png 1780825-20190829182201479-1238403745 %}

    当通过自定义函数创建对象时,对象的\_\_proto\_\_指向这个函数的原型,而自定义函数的prototype本质上就是一个空对象,都是被Object创建,所以它的\_\_proto\_\_指向的就是Object.prototype。**但是,Object.prototype确实一个特例——它的\_\_proto\_\_指向的是null**

    3. ```js
    function foo(x, y){return x+y}
    var foo = new Function("x","y","return x+y")

    函数也是一种对象,函数也有__proto__属性,每个函数都是Function的实例对象,包括Object函数,所以自定义函数Foo.__proto__指向Function.prototype,Object.__proto__指向Function.prototype。但是,这里Function.__proto__指向Function.prototype,原因是:Function也是一个函数,函数是一种对象,也有__proto__属性。既然是函数,那么它一定是被Function创建。所以Function是被自身创建的。所以它的__proto__指向了自身的Prototype

​ Function.prototype指向的对象也是一个普通的被Object创建的对象,它的__proto__是不是也指向Object.prototype

  1. 完整的关系如下

  • 几个问题

    1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
    1
    2
    3
    console.log(Fn.prototype instanceof Object) // true
    console.log(Object.prototype instanceof Object) // false
    console.log(Function.prototype instanceof Object) // true
    1. 所有函数都是Function的实例(包含Function)

      1
      console.log(Function.__proto__===Function.prototype)
    2. Object的原型对象是原型链尽头

    1
    console.log(Object.prototype.__proto__) // null

原型的属性

  1. 读取对象的属性值时: 会自动到原型链中查找

  2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值

  3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上

instanceof

instanceof是如何判断的?

  • 表达式: A instanceof B
  • 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
function Foo() {  }
var f1 = new Foo()
console.log(f1 instanceof Foo) // true
console.log(f1 instanceof Object) // true


console.log(Object instanceof Function) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
console.log(Function instanceof Object) // true

function Foo() {}
console.log(Object instanceof Foo) // false

执行上下文与执行上下文栈

变量提升和函数提升

ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分,js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升

变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。

当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明

执行上下文

  1. 代码分类(位置)

    • 全局代码
    • 函数(局部)代码
  2. 全局执行上下文

    • 在执行全局代码前将window确定为全局执行上下文
    • 对全局数据进行预处理
      • var定义的全局变量==>undefined, 添加为window的属性
      • function声明的全局函数==>赋值, 添加为window的方法
      • this==>赋值(window)
    • 开始执行全局代码
  3. 函数执行上下文

    • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)

    • 对局部数据进行预处理

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

      函数在定义的时候(不是调用的时候),就已经确定了函数体内部变量的作用域。

    • 开始执行函数体代码

执行上下文栈

  1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下window

作用域和作用域链

  1. 分类

    • 全局作用域

    • 函数作用域

    • 没有块作用域(ES6有了)

  2. 作用

    • 隔离变量,不同作用域下同名变量不会有冲突

作用域链

  • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  • 查找变量时就是沿着作用域链来查找的

查找一个变量的查找规则(自由变量跨作用域取值时,要去创建这个函数的作用域取值)

  1. 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2

  2. 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3

  3. 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

闭包

  1. 如何产生闭包?

    • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  2. 闭包到底是什么?

    • 使用chrome调试查看

    • 理解一: 闭包是嵌套的内部函数(绝大部分人)

    • 理解二: 包含被引用变量(函数)的对象(极少数人)

      注意: 闭包存在于嵌套的内部函数中

  3. 产生闭包的条件?

    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)
1
2
3
4
5
6
7
8
9
function fn1 () {
var a = 2
var b = 'abc'
function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
console.log(a)
}
// fn2()
}
fn1()

常见的闭包

  1. 将函数作为另一个函数的返回值
1
2
3
4
5
6
7
8
9
10
11
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
  1. 将函数作为实参传递给另一个函数调用
1
2
3
4
5
6
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('atguigu', 2000)

闭包的作用

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
// return a
}
function fn3() {
a--
console.log(a)
}
return fn3
}
var f = fn1()
f() // 1
f() // 0

闭包的生命周期

  1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
  2. 死亡: 在嵌套的内部函数成为垃圾对象时

自定义JS模块

JS模块:

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function myModule() {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}

//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function () {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}

//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()

闭包的缺点

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 容易造成内存泄露

解决

  • 能不用闭包就不用
  • 及时释放
1
2
3
4
5
6
7
8
9
10
11
function fn1() {
var arr = new Array[100000]
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()

f = null //让内部函数成为垃圾对象-->回收闭包

对象高级

创建对象

  1. Object构造函数模式
1
2
3
4
5
6
7
8
9
// 先创建空Object对象
var p = new Object()
p = {} //此时内部数据是不确定的
// 再动态添加属性/方法
p.name = 'Tom'
p.age = 12
p.setName = function (name) {
this.name = name
}
  • 套路: 先创建空Object对象, 再动态添加属性/方法
  • 适用场景: 起始时不确定对象内部数据
  • 问题: 语句太多
  1. 对象字面量模式
1
2
3
4
5
6
7
var p = {
name: 'Tom',
age: 12,
setName: function (name) {
this.name = name
}
}
  • 套路: 使用{}创建对象, 同时指定属性/方法
  • 适用场景: 起始时对象内部数据是确定的
  • 问题: 如果创建多个对象, 有重复代码
  1. 工厂模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createPerson(name, age) { //返回一个对象的函数===>工厂函数
var obj = {
name: name,
age: age,
setName: function (name) {
this.name = name
}
}

return obj
}

// 创建2个人
var p1 = createPerson('Tom', 12)
var p2 = createPerson('Bob', 13)
  • 套路: 通过工厂函数动态创建对象并返回
  • 适用场景: 需要创建多个对象
  • 问题: 对象没有一个具体的类型, 都是Object类型
  1. 自定义构造函数模式
1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name
this.age = age
this.setName = function (name) {
this.name = name
}
}
var p1 = new Person('Tom', 12)
  • 套路: 自定义构造函数, 通过new创建对象
  • 适用场景: 需要创建多个类型确定的对象
  • 问题: 每个对象都有相同的数据, 浪费内存
  1. 构造函数+原型的组合模式
1
2
3
4
5
6
7
8
9
function Person(name, age) { //在构造函数中只初始化一般属性
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}

var p1 = new Person('Tom', 23)
  • 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
  • 适用场景: 需要创建多个类型确定的对象

继承模式

  1. 原型链继承

    子类型的原型为父类型的一个实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//父类型
function Supper() {
this.supProp = 'Supper property'
}
Supper.prototype.showSupperProp = function () {
console.log(this.supProp)
}

//子类型
function Sub() {
this.subProp = 'Sub property'
}

// 子类型的原型为父类型的一个实例对象
Sub.prototype = new Supper()
// 让子类型的原型的constructor指向子类型
Sub.prototype.constructor = Sub
Sub.prototype.showSubProp = function () {
console.log(this.subProp)
}

var sub = new Sub()
sub.showSupperProp()
// sub.toString()
sub.showSubProp()
console.log(sub) // Sub

​ 2.借用构造函数继承(没有真正继承)

​ 在子类型构造函数中通用call()调用父类型构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age, price) {
Person.call(this, name, age) // 相当于: this.Person(name, age)
/*this.name = name
this.age = age*/
this.price = price
}

var s = new Student('Tom', 20, 14000)
console.log(s.name, s.age, s.price)
  1. 原型链+借用构造函数的组合继承

    利用原型链实现对父类型对象的方法继承

    *  利用call()借用父类型构建函数初始化相同属性
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}

function Student(name, age, price) {
Person.call(this, name, age) // 为了得到属性
this.price = price
}
Student.prototype = new Person() // 为了能看到父类型的方法
Student.prototype.constructor = Student //修正constructor属性
Student.prototype.setPrice = function (price) {
this.price = price
}

var s = new Student('Tom', 24, 15000)
s.setName('Bob')
s.setPrice(16000)
console.log(s.name, s.age, s.price)

线程机制和事件机制

浏览器内核

内核由很多模块组成

  • js引擎模块:复制js程序的编译和运行

    • html,css文档解析模块 : 负责页面文本的解析
    • dom/css模块 : 负责dom/css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制
    • 定时器模块 : 负责定时器的管理
    • 网络请求模块 : 负责服务器请求(常规/Ajax)
    • 事件响应模块 : 负责事件的管理

事件循环模型

  1. 所有代码分类
    • 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
    • 回调执行代码(异步代码): 处理回调逻辑
  2. js引擎执行代码的基本流程:
    • 初始化代码===>回调代码
  3. 模型的2个重要组成部分:
    • 事件(定时器/DOM事件/Ajax)管理模块
    • 回调队列
  4. 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行