Danmo的学习之路(Javascript高级)

基础总结深入

1.11

引用类型

  • Object: 任意对象
  • Function: 一种特别的对象(可以执行)
  • Array: 一种特别的对象(数值下标,内部数据是有序的)

判断数据类型

三种方法判断数据类型:typeof、instanceof、===

  • typeof:
    可以判断: undefined/ 数值 / 字符串 / 布尔值 / function
    不能判断: null与object object与array
  • instanceof:
    判断对象的具体类型
  • ===
    可以判断: undefined, null

typeof的返回值是一个字符串,如果要检查一个变量a是不是undefined,要用typeof判断,应该写console.log(typeof(a) === ‘undefined’)
typeof(null)的值是object,因此如果要检查一个变量是不是null,只能用===

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.b3==='function') // true

  console.log(typeof b1.b2[2]==='function')
  b1.b2[2](4)	//调用函数演示
  console.log(b1.b3()())	//调用函数演示

1.14

undefined与null

区别:

  • undefined代表定义未赋值
  • null定义并赋值了, 只是值为null

什么时候给变量赋值为null:

  • 初始赋值, 表明将要赋值为对象
  • 结束前, 让对象成为垃圾对象(被垃圾回收器回收)

内存、数据、变量

内存:

  • 内存条通电后产生的可储存数据的空间
  • 产生和死亡: 内存条(电路板)->通电->产生内存空间->存储数据->处理数据->断电->内存空间和数据都消失
  • 存放的数据:内部存储的数据、地址值
  • 分类:栈: 全局变量/局部变量;堆: 对象

变量:

  • 可变化的量, 由变量名和变量值组成
  • 每个变量都对应的一块小内存, 变量名用来查找对应的内存, 变量值就是内存中保存的数据

内存,数据, 变量三者之间的关系:

  • 内存用来存储数据的空间
  • 变量是内存的标识

JS引擎管理内存的方式

内存生命周期

  • 分配小内存空间, 得到它的使用权
  • 存储数据, 可以反复进行操作
  • 释放小内存空间

释放内存

  • 局部变量: 函数执行完自动释放
  • 对象: 赋值null成为垃圾对象->垃圾回收器回收

对象

必须使用[‘属性名’]的情况

  1. 属性名包含特殊字符: - 空格
  2. 属性名不确定
//情况一
  p['content-type'] = 'text/json'
  console.log(p['content-type'])

//情况二
  var propName = 'myAge'
  var value = 18

  p[propName] = value
  console.log(p[propName])

对象字面量和构造函数

var obj = {} 等价于 var obj = new Object(),
所以用对象字面量创建的对象,是Object函数的实例,

函数

call和apply在JS基础上已经记过笔记

回调函数

  1. 回调函数满足:
  • 自己定义
  • 没有立即调用
  • 最终它在某个时刻或某个条件下执行了
  1. 常见的回调函数
  • dom事件回调函数 ->发生事件的dom元素
  • 定时器回调函数 ->window
  • ajax请求回调函数(后面讲)
  • 生命周期回调函数(后面讲)

原型

博客/自我总结

可以参考以下三篇博客:
JavaScript中Function和Object的原型和原型链

JS中 Object.prototype 原型和原型链 详解

彻底理解什么是原型链,prototype和__proto__的区别(最清晰)

自己总结一下:

  • 在函数对象中存在原型对象prototype,在普通对象中没有prototype,但存在__proto__,所有对象都有__proto__属性
  • function定义的对象有prototype属性,使用new生成的对象没有prototype属性,存在__proto__
  • 特殊的是, Function.prototype没有prototype属性,在控制台输出undefined
  • 如果认为普通对象A是对象B的实例,A的__proto__属性等价于B的prototype属性
  • Object.__proto__指向Function.prototype(HBuilder的控制台输出结果为function Empty() {}),但是Object.prototype.__proto__指向null

prototype是函数对象自带的一个属性,比如console.log(Object.prototype),控制台打印[object Object];console.log(Function.prototype),控制台打印function Empty() {}。给人的感觉是,打印 自己的“本质”。prototype后又可以继续访问它的__proto__属性,如console.log(Object.prototype.proto),控制台打印null。

而__proto__是普通对象自带的一个属性,普通对象.__proto__即为函数对象.prototype。__proto__给人的感觉是,要追溯它的下一级,比如用构造函数得到的对象,它的__proto__就是new后边的函数,又比如Object其实是一个函数对象,那么console.log(Object.proto)会在控制台打印function Empty() {},即它可等价为Function.prototype。

constructor

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

console.log(Date.prototype.constructor===Date)	//true
console.log(Date.prototype.constructor)		//function Date() { [native code] }

关于construcor,可以看这篇博客:
javascript 对象中的 constructor属性的作用

显式原型与隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 内存结构(图)
  5. 总结:
  • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象(该“空”指的是没有程序员指定的属性或方法,不是什么都没有)
  • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

内存结构图:
在这里插入图片描述

原型链

在这里插入图片描述

执行上下文和作用域

执行上下文

代码分类(位置)

  • 全局代码
  • 函数(局部)代码

全局执行上下文

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

函数执行上下文

  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  • 对局部数据进行预处理
  • 形参变量->赋值(实参)->添加为执行上下文的属性
  • arguments->赋值(实参列表), 添加为执行上下文的属性
  • var定义的局部变量->undefined, 添加为执行上下文的属性
  • function声明的函数 ->赋值(fun), 添加为执行上下文的方法
  • this->赋值(调用函数的对象)
  • 开始执行函数体代码

执行上下文对象个数=调用函数的次数+1(多出的1是window,即全局执行上下文)

<script type="text/javascript">
  var a = 10;
  var bar = function (x) {
    var b = 5;
    foo(x + b);
  };
  var foo = function (y) {
    var c = 5;
    console.log(a + c + y);
  }
  bar(10);//调用bar算一次,bar中还要调用foo再算一次,共2次
  bar(10);//同理,共2次
</script>

这一段代码中共4+1=5个执行上下文对象

执行上下文栈

  1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下window
  1. 依次输出什么?
    gb: undefined
    fb: 1
    fb: 2
    fb: 3
    fe: 3
    fe: 2
    fe: 1
    ge: 1
  2. 整个过程中产生了几个执行上下文? 5
  console.log('gb: '+ i);
  var i = 1;
  foo(1);
  function foo(i) {
    if (i == 4) {
      return;
    }
    console.log('fb:' + i);
    foo(i + 1) //递归调用: 在函数内部调用自己
    console.log('fe:' + i);
  }
  console.log('ge: ' + i);

作用域链

理解

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

查找一个变量的查找规则

  • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

区别与联系

区别1

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

区别2

  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

联系

  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境->全局作用域
  • 函数上下文环境->对应的函数使用域

1.15

闭包

彻底理解JS中的闭包

闭包,看这一篇就够了——带你看透闭包的本质,百发百中

定义

  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  • 闭包是嵌套的内部函数,是包含被引用变量(函数)的对象
  • 闭包存在于嵌套的内部函数中,执行函数定义就会产生闭包(不用调用内部函数)
  • 产生闭包的条件:
    • 函数嵌套
    • 内部函数引用了外部函数的数据(变量/函数)

常见闭包

将函数作为另一个函数的返回值

  function fn1() {
    var a = 2;
    function fn2() {
      a++;
      console.log(a);
    }
    return fn2;
  }
  var f = fn1();
  f(); // 3
  f(); // 4

将函数作为实参传递给另一个函数调用

function showDelay(msg, time) {
    setTimeout(function () {
      alert(msg);
    }, time);
  }
  showDelay('atguigu', 2000);

闭包的作用

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中,延长了局部变量的生命周期(类比static)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:

  1. 函数执行完后, 函数内部声明的局部变量一般不存在, 存在于闭包中的变量才可能存在
  2. 在函数外部不能直接访问函数内部的局部变量吗, 但可以通过闭包让外部操作它

闭包的生命周期

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

  function fn1() {
    //此时闭包已经产生(函数提升, 内部函数对象已经创建了)
    var a = 2;
    function fn2 () {
      a++;
      console.log(a);
    }
    return fn2;
  }
  var f = fn1();
  f() // 3;
  f() // 4;
  f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象)

闭包的应用

定义JS模块

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

1.16

对象创建模式

工厂模式

(这部分在JS基础中讲过,此处是为了和下方的自定义构造函数放在一起)

  • 套路: 通过工厂函数动态创建对象并返回
  • 适用场景: 需要创建多个对象
  • 问题: 对象没有一个具体的类型,都是Object类型
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);

  // p1和p2是Object类型

自定义构造函数模式

  • 套路: 自定义构造函数, 通过new创建对象
  • 适用场景: 需要创建多个类型确定的对象
  • 问题: 每个对象都有相同的数据,浪费内存
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.setName = function (name) {
      this.name = name;
    }
  }
  var p1 = new Person('Tom', 12);
  p1.setName('Jack');
  console.log(p1.name, p1.age);
  console.log(p1 instanceof Person);
  //p1确定的类型是Person

  function Student (name, price) {
    this.name = name;
    this.price = price;
  }
  var s = new Student('Bob', 13000);
  console.log(s instanceof Student);
  //p2确定的类型是Student
  var p2 = new Person('JACK', 23);
  console.log(p1, p2);

构造函数+原型 的组合模式

  • 套路: 自定义构造函数,属性在函数中初始化, 方法添加到原型上
  • 适用场景: 需要创建多个类型确定的对象
function Person(name, age) { 
//属性在函数中初始化
    this.name = name;
    this.age = age;
  }
  Person.prototype.setName = function (name) {
  //向原型中添加方法
    this.name = name;
  }

  var p1 = new Person('Tom', 23);
  var p2 = new Person('Jack', 24);
  console.log(p1, p2);

1.17

继承模式

原型链继承

(这一块很重要,笔试/面试可能会考察)
套路

  1. 定义父类型构造函数
  2. 给父类型的原型添加方法
  3. 定义子类型的构造函数
  4. 创建父类型的对象赋值给子类型的原型
  5. 将子类型原型的构造属性设置为子类型(参考:阮一峰的博客
  6. 给子类型原型添加方法
  7. 创建子类型的对象: 可以调用父类型的方法

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

//1.定义父类型构造函数
  function Supper() {
    this.supProp = 'Supper property';
  }
  
//2.给父类型的原型添加方法
  Supper.prototype.showSupperProp = function () {
    console.log(this.supProp);
  }
//3. 定义子类型的构造函数
  function Sub() {
    this.subProp = 'Sub property';
  }
//4. 创建父类型的对象赋值给子类型的原型
  Sub.prototype = new Supper();
  
//5. 将子类型原型的构造属性设置为子类型
  Sub.prototype.constructor = Sub;
  
//6. 给子类型原型添加方法
  Sub.prototype.showSubProp = function () {
    console.log(this.subProp);
  }
  
//7. 创建子类型的对象: 可以调用父类型的方法
  var sub = new Sub();
  sub.showSupperProp(); //"Supper property"
  // sub.toString();
  sub.showSubProp();	//"Sub property"

  console.log(sub);  // Sub
/*
	Sub
	subProp: "Sub property"
	__proto__: Supper
*/

借用构造函数继承

套路:

  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造(call,或直接用this)

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

// 1. 定义父类型构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
  }

//2. 定义子类型构造函数  
 function Student(name, age, salary) {
//3.在子类型构造函数中调用父类型构造
   Person.call(this, name, age) ; 
   // 相当于: this.Person(name, age);
   this.salary = salary;
 }
 var s = new Student('Tom', 20, 14000);
 console.log(s.name+" "+s.age+" "+s.salary);	//Tom 20 14000

组合继承

原型链+借用构造函数的组合继承

  1. 利用原型链实现对父类型对象的方法继承
  2. 利用call()借用父类型构建函数初始化相同属性
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下边一行加:s.__proto__.setPrice = ... 
 //或者直接s.setPrice...

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

线程机制与事件机制

进程与线程

进程:程序的一次执行,它占有一片独有的内存空间

线程: CPU的基本调度单位, 是程序执行的一个完整流程

进程与线程:

  • 一个进程中一般至少有一个运行的线程: 主线程
  • 一个进程中也可以同时运行多个线程,,称程序是多线程运行的
  • 一个进程内的数据可以供其中的多个线程直接共享
  • 多个进程之间的数据是不能直接共享的

单进程浏览器:firefox、老版IE
多进程浏览器:chrome、新版IE

如何查看浏览器是否是多进程运行:任务管理器->进程

浏览器都是多线程运行的

浏览器内核

浏览器内核:支持浏览器运行的最核心的程序

不同浏览器的内核:Chrome, Safari: webkit;firefox: Gecko;IE: Trident;360,搜狗等国内浏览器: Trident + webkit

内核由很多模块组成:

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

定时器引发的思考

定时器并不能保证真正定时执行,一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)

 document.getElementById('btn').onclick = function () {
   var start = Date.now();
   console.log('启动定时器前...');
   setTimeout(function () {
     console.log('定时器执行了', Date.now()-start);
   }, 200);
   console.log('启动定时器后...');

   // 做一个长时间的工作,发现定时器执行的时间远远超过200ms
   for (var i = 0; i < 1000000000; i++) {
   }
 }
 //修改i之后的时间,先ctrl+s保存,在浏览器中刷新,再点击按钮,才会显示新的运行时间

定时器回调函数在主线程执行,js是单线程的

定时器是如何实现的:事件循环模型

JS是单线程的

如何证明js执行是单线程的?

  • setTimeout()的回调函数是在主线程执行的
  • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

为什么js要用单线程模式, 而不用多线程模式?

  • JavaScript的单线程,与它的用途有关。
  • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

代码的分类:

  • 初始化代码
  • 回调代码

JS引擎执行代码的基本流程:

  • 执行初始化代码: 包含一些特别的代码、回调函数(异步执行)
  • 设置定时器
  • 绑定事件监听
  • 发送ajax请求
  • 后面在某个时刻才会执行回调代码
setTimeout(function () {
  console.log('timeout 2rd');
  alert('Second');
}, 2000)
setTimeout(function () {
  console.log('timeout 1st');
  alert('First');
}, 1000)
setTimeout(function () {
  console.log('timeout() 0');
}, 0)
function fn() {
  console.log('fn()');
}
fn();
console.log('alert()之前');
alert('------');
//暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
console.log('alert()之后');

该程序执行的次序:

  1. 执行fn函数,在控制台输出fn()
  2. 控制台输出alert()之前
  3. 弹出’------’
  4. 控制台输出alert()之后
  5. 控制台输出timeout() 0
  6. 控制台输出timeout 1st 弹出First
  7. 控制台输出timeout 2nd 弹出Second

事件循环模型

所有代码分类

  • 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
  • 回调执行代码(异步代码): 处理回调逻辑

JS引擎执行代码的基本流程:初始化代码->回调代码

模型的2个重要组成部分:

  • 事件(定时器/DOM事件/Ajax)管理模块
  • 回调队列

模型的运转流程

  • 执行初始化代码,将事件回调函数交给对应模块管理
  • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
  • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
function fn1() {
  console.log('fn1()')
}
fn1()
document.getElementById('btn').onclick = function () {
  console.log('点击了btn')
}
setTimeout(function () {
  console.log('定时器执行了')
}, 2000)
function fn2() {
  console.log('fn2()')
}
fn2()

控制台依次打印:
fn1()
fn2()
定时器执行了

点击按钮,打印:点击了btn

Web Workers

这一部分暂时先不学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值