JavaScript高级

JavaScript高级

基础总结深入

1.数据类型

1.1. 分类(2大类)

  • 基本(值)类型
    • Number: 任意数值
    • String: 任意文本
    • Boolean: true/false
    • undefined: undefined
    • null: null
  • 对象(引用)类型
    • Object: 任意对象
    • Array: 特别的对象类型(下标/内部数据有序)
    • Function: 特别的对象类型(可执行)

1.2. 判断

  • typeof:

    • 返回的是数据类型的字符串表达形式
    • 可以区别: 数值, 字符串, 布尔值, undefined, function
    • 不能区别: null与对象, 一般对象与数组
  • instanceof

    • 专门用来判断对象数据的类型: Object, Array与Function
    • A instanceof B : 判断A是不是B的实例
  • ===

    • 可以判断: undefined和null
//1.基本数据类型
  var a
  console.log(a, typeof a, a===undefined) // undefined 'undefined' true
  console.log(a===typeof a) // false

  a = 3
  console.log(typeof a === 'number') //true
  a = 'atguigu'
  console.log(typeof a === 'string') //true
  a = true
  console.log(typeof a === 'boolean') //true

  a = null
  console.log(a===null) // true
  console.log(typeof a) // 'object'
  
//2.对象类型
  var b1 = {
    b2: [2, 'abc', console.log],
    b3: function () {
      console.log('b3()')
    }
  }
  console.log(b1 instanceof Object, typeof b1) // true 'object'
  console.log(b1.b2 instanceof Array, typeof b1.b2) // true 'object'
  console.log(b1.b3 instanceof Function, typeof b1.b3) // true 'function'

  console.log(typeof b1.b2[2]) // 'function'
  console.log(b1.b2[2]('abc')) // 'abc' undefined

1.3. undefined与null的区别?

  • undefined代表没有赋值
  • null代表赋值了, 只是值为null
  var a1
  var a2 = null
  console.log(a1, a2)

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

  var a = null //a将指向一个对象, 但对象此时还没有确定
  a = null //让a指向的对象成为垃圾对象

  //初始
  var a3 = null
    //中间
  var name = 'Tom'
  var age = 12
  a3 = {
    name: name,
    age: age
  }
    //结束
  a3 = null

1.5. 严格区别变量类型与数据类型?

  • js的变量本身是没有类型的, 变量的类型实际上是变量内存中数据的类型
  • 变量类型:
    • 基本类型: 保存基本类型数据的变量
    • 引用类型: 保存对象地址值的变量
  • 数据对象
    • 基本类型
    • 对象类型

2.数据_变量_内存

2.1. 什么是数据?

  • 存储于内存中代表特定信息的’东东’, 本质就是0101二进制
  • 具有可读和可传递的基本特性
  • 万物(一切)皆数据, 函数也是数据
  • 程序中所有操作的目标: 数据
    • 算术运算
    • 逻辑运算
    • 赋值
    • 调用函数传参

2.2. 什么是内存?

  • 内存条通电后产生的存储空间(临时的)
  • 产生和死亡: 内存条(集成电路板)>通电>产生一定容量的存储空间==>存储各种数据==>断电==>内存全部消失
  • 内存的空间是临时的, 而硬盘的空间是持久的
  • 一块内存包含2个数据
    • 内部存储的数据(一般数据/地址数据)
    • 内存地址值数据
  • 内存分类
    • 栈: 全局变量, 局部变量 (空间较小)
    • 堆: 对象 (空间较大)

2.3. 什么是变量?

  • 值可以变化的量, 由变量名与变量值组成
  • 一个变量对应一块小内存, 变量名用来查找到内存, 变量值就是内存中保存的内容

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

  • 内存是一个容器, 用来存储程序运行需要操作的数据

  • 变量是内存的标识, 我们通过变量找到对应的内存, 进而操作(读/写)内存中的数据

    问题: var a = xxx, a内存中到底保存的是什么?

  • xxx是一个基本数据: a保存的就是这个数据

  • xxx是一个对象: a保存的就是这个地址值

  • xxx是一个变量: a保存的都是xxx的内容(可能是基本数据,也可能是地址值)

2.5.关于引用变量赋值问题

  • 2个引用变量指向同一个对象, 通过一个引用变量修改对象内部数据, 另一个引用变量看到的是修改之后的数据
  var obj1 = {name:'Jack'}
  var obj2 = obj1
  obj2.name = 'Tom'
  console.log(obj1.name)//Tom
  function f1(obj) {
    obj.age = 12
  }
  f1(obj2)
  console.log(obj1.age)//12
  • 2个引用变量指向同一个对象,让一个引用变量指向另一个对象, 另一个引用变量还是指向原来的对象
var obj3 = {name: 'Tom'}
  var obj4 = obj3
  obj3 = {name: 'JACK'}
  console.log(obj4.name)//Tom
  function f2(obj) {
    obj = {name: 'Bob'}
  }
  f2(obj4)
  console.log(obj3.name)//JACK
  console.log(obj4.name)//Tom

2.6.问题: 在js调用函数时传递变量参数时, 是值传递还是引用传递

  • 只有值传递, 没有引用传递, 传递的都是变量的值, 只是这个值可能是基本数据, 也可能是地址(引用)数据
  • 如果后一种看成是引用传递, 那就值传递和引用传递都可以有

2.7.JS引擎如何管理内存?

  1. 内存生命周期
    1). 分配需要的内存
    2). 使用分配到的内存
    3). 不需要时将其释放/归还
  2. 释放内存
    局部变量:函数执行完自动释放
    对象:成为垃圾对象==>垃圾回收器回收
    * 为执行函数分配的栈空间内存: 函数执行完自动释放
    * 存储对象的堆空间内存: 当内存没有引用指向时, 对象成为垃圾对象, 垃圾回收器后面就会回收释放此内存

3.对象

3.1.什么是对象

  • 代表现实中的某个事物, 是该事物在编程中的抽象
  • 多个数据的集合体(封装体)
  • 用于保存多个数据的容器
var obj = {
	name:'Tom',
	age:'12'
}

3.2. 为什么要用对象?

  • 便于对多个数据进行统一管理

3.3. 对象的组成

  • 属性
    • 代表现实事物的状态数据
    • 由属性名和属性值组成
    • 属性名都是字符串类型, 属性值是任意类型
  • 方法
    • 代表现实事物的行为数据
    • 特别的属性==>属性值是函数
var obj = {
	name:'Tom',
	age:'12',
	setName:function(name){
		this.name=name;
	},
	setAge:function(age){
		this.age=age;
	}
}

3.4. 如何访问对象内部数据?

  • .属性名: 编码简单, 但有时不能用
  • [‘属性名’]: 编码麻烦, 但通用
  console.log(p.name, p['age'])//Tom 12
  p.setName('Jack')
  p['setAge'](23)
  console.log(p['name'], p.age)//Jack 23

问题: 什么时候必须使用[‘属性名’]的方式?

  • 属性名不是合法的标识名
    包含特殊字符: - 空格
  • 属性名不确定
var p = {}
var propName = 'myAge'
var value = 18
//p.propName = value     不可用
p[propName] = value
console.log(p[propName])//18

4.函数

4.1. 什么是函数?

  • 具有特定功能的n条语句的封装体
  • 只有函数是可执行的, 其它类型的数据是不可执行的
  • 函数也是对象
  /*
  编写程序实现以下功能需求:
    1. 根据年龄输出对应的信息
    2. 如果小于18, 输出: 未成年, 再等等!
    3. 如果大于60, 输出: 算了吧!
    4. 其它, 输出: 刚好!
  */
  function show(age){
    if(age<18){
      console.log('未成年, 再等等!')
    }else if(age>60){
      console.log('算了吧!')
    }else{
      console.log('刚好!')
    }
  }
  showInfo(10)
  showInfo(22)
  showInfo(88)

4.2. 为什么要用函数?

  • 提高代码复用
  • 便于阅读和交流

4.3. 如何定义函数?

  • 函数声明
  • 表达式
function fn1() {//声明式
    console.log("fn1()")
  }
  var fn2 = function () {//表达式
    console.log("fn2()")
  }
  fn1()
  fn2()

4.4. 如何调用(执行)函数?

  • test()
  • new test()
  • obj.test()
  • test.call/apply(obj):临时让test成为obj的方法进行调用
  var obj = {}
  function test2 (){
    this.xxx = 'atiguigu'
  }
  //obj.test2  不能直接调用,因为obj属性没有这个方法
  test2.call(obj) //可以让一个函数成为指定任意对象的方法进行调用
  console.log(obj.xxx)

5.回调函数

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

  • 你定义的
  • 你没有直接调用
  • 但最终它执行了(在特定条件或时刻)

5.2. 常见的回调函数?

  • DOM事件函数

  • 定时器函数

  • ajax回调函数(后面学)

  • 生命周期回调函数(后面学)

//1. DOM事件函数
  var btn = document.getElementById('btn')
  btn.onclick = function () {
    alert(this.innerHTML)
  }

  //2. 定时器函数
  setInterval(function () {
    alert('到点啦!')
  }, 2000)

6.IIFE

6.1. 理解

  • 全称: Immediately-Invoked Function Expression 立即调用函数表达式
  • 别名: 匿名函数自调用
  (function (){
    console.log("……")
  })()

6.2. 作用

  • 隐藏内部实现
  • 不污染外部命名空间
(function (i) {
    var a = 4
    function fn() {
      console.log('fn ', i+a)
    }
    fn()
  })(3)//fn 7

7.函数中的this

7.1. this是什么?

  • 所有函数内部都有一个变量this
  • 一个关键字, 一个内置的引用变量
  • 在函数中都可以直接使用this
  • this代表调用函数的当前对象
  • 在定义函数时, this还没有确定, 只有在执行时才动态确定(绑定)的

7.2. 如何确定this的值?

  • test():window
  • obj.test():obj
  • new test():新创建的对象
  • test.call(obj):obj
    前置知识:
  • 本质上任何函数在执行时都是通过某个对象调用的
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();

函数高级

1.原型与原型链

1.1.原型(prototype)

1.1.1. 函数的prototype属性(图)
  • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  • 原型对象中有一个属性constructor, 它指向函数对象
  console.log(Date.prototype, typeof Date.prototype)
  function fn() {

  }
  console.log(fn.prototype, typeof fn.prototype)

protype1
protype2

  • 原型对象中有一个属性constructor, 它指向函数对象
  console.log(Date.prototype.constructor===Date)//true
  console.log(fn.prototype.constructor===fn)//true
1.1.2. 给原型对象添加属性(一般都是方法)
  • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
function F() {

  }
  F.prototype.age = 12 //添加属性
  F.prototype.setAge = function (age) { // 添加方法
    this.age = age
  }
  // 创建函数的实例对象
  var f = new F()
  console.log(f.age)//12
  f.setAge(23)
  console.log(f.age)//23

1.2.显式原型与隐式原型

1.2.1. 每个函数function都有一个prototype,即显式原型
1.2.2. 每个实例对象都有一个__proto__,可称为隐式原型
    function Fn() {
      
    }
    console.log(Fn.prototype)//1.显式
    var fn = new Fn()//内部语句:this.__proto__=Fn.prototype
    console.log(fn.__proto__)//2.隐式

在这里插入图片描述

1.2.3. 对象的隐式原型的值为其对应构造函数的显式原型的值
console.log(fn.__proto__===Fn.prototype)//true
1.2.4. 内存结构(图)

在这里插入图片描述
增加属性
在这里插入图片描述

1.2.5. 总结:
  • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

1.3.原型链

1.3.1. 原型链(图解)
  • 访问一个对象的属性时,
    • 先在自身属性中查找,找到返回
    • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
    • 如果最终没找到, 返回undefined
  • 别名: 隐式原型链
  • 作用: 查找对象的属性(方法)
function Fn() {
    this.test1 = function () {
      console.log('test1()')
    }
  }
  Fn.prototype.test2 = function () {
    console.log('test2()')
  }
  var fn = new Fn()

  fn.test1()//test1()
  fn.test2()//test2()
  console.log(fn.toString())//[object,object]
  fn.test3()//报错

在这里插入图片描述

1.3.2 原型链特点

原型链的特点主要有两个:

  1. 由于原型链的存在,属性查找的过程不再是只查找自身的原型对象,而是会沿着整个原型链一直向上,直到追溯到Object.prototype.如果Object.prototype上也找不到该属性,则返回undefined,如果期间在实例本身或者某个原型对象上找到了该属性,会直接返回结果,因此也会存在属性覆盖的问题。
    由于特点1的存在,我们在生成定义对象的实例时,也可以调用到某些未调用到某些未在定义构造函数上的函数,例如toString()方法
    2.由于属性查找会经历整个原型链,因此查找的链路越长,对性能的影响越大。
1.3.3.属性区分

对象属性的寻找往往会涉及这个原型链,那么该怎么区分属性是实例自身还是从原型链中继承的呢?
Object()构造函数的原型对象中提供了一个**hasOwnProperty()**函数,用于判断是否为自身拥有的。

function Person(name) {
      //实例属性name
      this.name = name;
    }

    Person.prototype.age = '18';
    var person = new Person('name');
    console.log(person.hasOwnProperty('name'));//true
    console.log(person.hasOwnProperty('age'));//false
1.3.4.内置构造函数

js内部有一些特定的内置构造函数,他们本身的__proto__属性都统一指向Function.prototype

	console.log(String.__proto__ === Function.prototype);
    console.log(Number.__proto__ === Function.prototype);
    console.log(Array.__proto__ === Function.prototype);
    console.log(Object.__proto__ === Function.prototype);
    console.log(Date.__proto__ === Function.prototype);
    console.log(Function.__proto__ === Function.prototype);
    //全为true
1.3.5.__proto__属性

在js的原型链体系中,最重要的莫过于__proto__属性,只有通过它才能将原型链串联起来。

    var str = new String('linjuejie');
    console.log(str);

控制器结果
所以我们就能通过str来调用对应的toString()方法,trim()方法等。

    Function.prototype.a = 'a';
    Object.prototype.b = 'b';
    function Person() {

    };
    var  p = new Person();
    console.log('p.a:',p.a);//undefined
    console.log('p.b:',p.b);//b

1.4.探索instanceof

1.4.1.instanceOf是怎么判断的

表达式:A instanceOf B
如果B函数的显式原型对象在A对象的原型链上,返回true,否则返回false

	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 Object);//true
    console.log(Function instanceof Function);//true

    function foo() {

    };
    console.log(Object instanceof foo);//false
   

1.5.常见面试题

var A = function() {

  }
  A.prototype.n = 1

  var b = new A()
  console.log(b.__proto__)

  A.prototype = {
    n: 2,
    m: 3
  }

  var c = new A()
  console.log(b.n, b.m, c.n, c.m)//1 undefined 2 3
  var F = function(){};
  Object.prototype.a = function(){
    console.log('a()')
  };
  Function.prototype.b = function(){
    console.log('b()')
  };
  var f = new F();
  f.a()
  // f.b()报错
  F.a()
  F.b()

2.执行上下文与执行上下文栈

2.1.变量提升与函数提升

2.1.1. 变量声明提升
  • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  • 值: undefined
  var a = 4
  function fn () {
    console.log(a)
    var a = 5
  }
  fn()//undefined
  /*变量提升*/
  console.log(a1) //可以访问, 但值是undefined

  var a1 = 3
2.1.2. 函数声明提升
  • 通过function声明的函数, 在之前就可以直接调用
  • 值: 函数定义(对象)
/*函数提升*/
  a2() // 可以直接调用

  function a2() {
    console.log('a2()')
  }
2.1.3. 问题: 变量提升和函数提升是如何产生的?

前置知识:执行上下文

  • 全局执行上下文:(全局代码执行时JS所做的前置准备工作)
  1. 在执行全局代码前将window确定为全局执行上下文
  2. 对全局数据进行预处理
    1. var定义的全局变量赋值为undefined,并且添加为window的属性;
    2. function申明的全局函数复制(fun),添加为window的方法;
    3. this赋值window;
  3. 开始执行全局代码

所以全局代码并不是像我们书写的顺序而执行,而是通过系统的预处理更改了代码顺序后执行。

2.2.执行上下文

2.2.1. 代码分类(位置)
  • 全局代码
  • 函数代码
2.2.2. 全局执行上下文
  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
    • var定义的全局变量==>undefined, 添加为window的属性
    • function声明的全局函数==>赋值(fun), 添加为window的方法
    • this==>赋值(window)
  • 开始执行全局代码
  console.log(a1,window.a1)//undefined undefined
  console.log(a2)//undefined
  console.log(a3)
  // console.log(a4)
  console.log(this)

  var a1 = 3
  var a2 = function () {
    console.log('a2()')
  }
  function a3() {
    console.log('a3()')
  }
  a4 = 4

在这里插入图片描述

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

    // console.log(b3)

    var b1 = 5
    function b2 () {

    }
    b3 = 6
  }
  fn()

在这里插入图片描述

2.3.执行上下文栈

2.3.1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
2.3.2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
2.3.3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
2.3.4. 在当前函数执行完后,将栈顶的对象移除(出栈)
2.3.5. 当所有的代码执行完后, 栈中只剩下window
                            //1. 进入全局执行上下文
  var a = 10
  var bar = function (x) {
    var b = 5
    foo(x + b)              //3. 进入foo执行上下文
  }
  var foo = function (y) {
    var c = 5
    console.log(a + c + y)
  }
  bar(10)                    //2. 进入bar函数执行上下文

3.作用域与作用域链

3.1.作用域

3.1.1. 理解
  • 就是一块"地盘", 一个代码段所在的区域
  • 它是静态的(相对于上下文对象), 在编写代码时就确定了
3.1.2. 分类
  • 全局作用域
  • 函数作用域
  • 没有块作用域(ES6有了)
3.1.3. 作用
  • 隔离变量,不同作用域下同名变量不会有冲突
if(true) {
    var c = 3
  }
  console.log(c)//3

  var a = 10,
    b = 20
  function fn(x) {
    var a = 100,
      c = 300;
    console.log('fn()', a, b, c, x)//fn() 100 20 300 10
    function bar(x) {
      var a = 1000,
        d = 400
      console.log('bar()', a, b, c, d, x)
    }

    bar(100)//bar() 1000 20 300 400 100
    bar(200)//bar() 1000 20 300 400 200
  }
  fn(10)

3.2.作用域与执行上下文

3.2.1. 区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
3.2.2. 区别2
  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
3.2.3. 联系
  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域

3.3.作用域链

3.3.1. 理解
  • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  • 查找变量时就是沿着作用域链来查找的
3.3.2. 查找一个变量的查找规则
  • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
  var a = 1
  function fn1() {
    var b = 2
    function fn2() {
      var c = 3
      console.log(c)
      console.log(b)
      console.log(a)
      console.log(d)
    }
    fn2()
  }
  fn1()

3.4.常见面试题

  var x = 10;
  function fn() {
    console.log(x);
  }
  function show(f) {
    var x = 20;
    f();
  }
  show(fn);//10

x的作用域在定义时就已经确定

  var fn = function () {
    console.log(fn)
  }
  fn()//fn的方法

  var obj = {
    fn2: function () {
     console.log(fn2)//报错
     //console.log(this.fn2)
    }
  }
  obj.fn2()

4.闭包

4.1.理解闭包

4.1.1. 如何产生闭包?
  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
4.1.2. 闭包到底是什么?
  • 使用chrome调试查看
  • 理解一: 闭包是嵌套的内部函数(绝大部分人)
  • 理解二: 包含被引用变量(函数)的对象(极少数人)
  • 注意: 闭包存在于嵌套的内部函数中
4.1.3. 产生闭包的条件?
  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)
  function fn1 () {
    var a = 2
    var b = 'abc'
    function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
      console.log(a)
    }
  }
  fn1()
  //fn2()的闭包中包含a但不包含b,执行函数定义就会产生闭包(不需要调用函数)

4.2.常见的闭包

4.2.1. 将函数作为另一个函数的返回值
function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
    }
    return fn2
}
  var f = fn1()
  f() // 3
  f() // 4

4.2.2. 将函数作为实参传递给另一个函数调用
  function showDelay(msg, time) {
    setTimeout(function () {
      alert(msg)
    }, time)
  }
  showDelay('atguigu', 2000)

4.3.闭包的作用

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

4.4.闭包的生命周期

4.4.1.产生

在嵌套内部函数定义执行完时就产生了

4.4.2.死亡

在嵌套的内部函数成为垃圾对象时

4.5.闭包的应用

定义JS模块
  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
//myModule.js
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
  }
  //html页面
  var module = myModule()
  module.doSomething()
  module.doOtherthing()
}

(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
  }
})()

//html
  myModule2.doSomething()
  myModule2.doOtherthing()

4.6.闭包的缺点及解决

4.6.1. 缺点
  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 容易造成内存泄露
4.6.2. 解决
  • 能不用闭包就不用
  • 及时释放
function fn1() {
    var arr = new Array[100000]
    function fn2() {
      console.log(arr.length)
    }
    return fn2
  }
  var f = fn1()
  f()

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

4.7.常见面试题

 var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());  //?  the window


  //代码片段二
  var name2 = "The Window";
  var object2 = {
    name2 : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name2;
      };
    }
  };
  alert(object2.getNameFunc()()); //?  my object
  function fun(n,o) {
    console.log(o)
    return {
      fun:function(m){
        return fun(m,n)
      }
    }
  }
  var a = fun(0)//fun(0,undefined)
  
  a.fun(1)//return fun(1,0)
  a.fun(2)
  a.fun(3)//undefined,0,0,0

  var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
  //.fun(2):fun(1,0).fun(2) return fun(2,1)

  var c = fun(0).fun(1)//fun(1,0)
  c.fun(2)//
  c.fun(3)//undefined,0,1,1

对象高级

1.对象创建模式

1.1.Object构造函数模式

  • 套路: 先创建空Object对象, 再动态添加属性/方法
  • 适用场景: 起始时不确定对象内部数据
  • 问题: 语句太多
// 先创建空Object对象
  var p = new Object()
  p = {} //此时内部数据是不确定的
  // 再动态添加属性/方法
  p.name = 'Tom'
  p.age = 12
  p.setName = function (name) {
    this.name = name
  }

  //测试
  console.log(p.name, p.age)
  p.setName('Bob')
  console.log(p.name, p.age)

1.2对象字面量

  • 套路: 使用{}创建对象, 同时指定属性/方法
  • 适用场景: 起始时对象内部数据是确定的
  • 问题: 如果创建多个对象, 有重复代码
var p = {
    name: 'Tom',
    age: 12,
    setName: function (name) {
      this.name = name
    }
  }

  //测试
  console.log(p.name, p.age)
  p.setName('JACK')
  console.log(p.name, p.age)

  var p2 = {  //如果创建多个对象代码很重复
    name: 'Bob',
    age: 13,
    setName: function (name) {
      this.name = name
    }

1.3.工厂模式

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

  function createStudent(name, price) {
    var obj = {
      name: name,
      price: price
    }
    return obj
  }
  var s = createStudent('张三', 12000)
  // s也是Object

1.4.自定义构造函数模式

  • 套路: 自定义构造函数, 通过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)

  function Student (name, price) {
    this.name = name
    this.price = price
  }
  var s = new Student('Bob', 13000)
  console.log(s instanceof Student)

  var p2 = new Person('JACK', 23)
  console.log(p1, p2)

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

  • 套路: 自定义构造函数, 属性在函数中初始化, 方法添加到原型上
  • 适用场景: 需要创建多个类型确定的对象
  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)//方法在原型上,是同一个方法

2.继承模式

2.1.原型链继承

继承作为面向对象语言的三大特性之一,三大特性是(封装,继承,多态),可以在不影响父类对象实现的情况下,使得子类对象具有父类对象的特性,同时还能在不影响父类对象行为的情况下拓展子类独有的属性,为编码带来了极大的便利。

虽然JavaScript不是一门面向对象的语言,不具备继承的特性,但是我们可以通过某些方式间接实现继承,从而能利用继承的优势,增强代码复用性与拓展性。

  function Animal(name) {
    this.type = 'animal';
    this.name = name || '动物';

    this.sleep = function () {
      console.log(this.name + '正在睡觉!');
    }
  }

  //原型链上的属性和方法
  Animal.prototype.age = 5;
  Animal.prototype.eat = function (food) {
    console.log(this.name + "正在吃" + food);
  }

  //  子类
  function Cat(name) {
    this.name = name;
  }

  //原型链继承
  Cat.prototype = new Animal();
  //很关键的一句,将Cat的构造函数指向自身
  var cat = new Cat('加菲猫');
  console.log(cat.type);//animal
  console.log(cat.name);//加菲猫
  cat.sleep();//加菲猫正在睡觉!
  console.log(cat.age);//5
  cat.eat('猫粮');//加菲猫正在吃猫粮

  var animal = new Animal();
  console.log(animal.type);//animal
  console.log(animal.name);//动物
  animal.sleep();//动物正在睡觉!
  console.log(animal.age);//5
  animal.eat('肉类');//动物正在吃肉类

打印cat
优点

  1. 简单,容易实现
    只需要设置子类的prototype属性为父类的实例即可,实现起来简单。
  2. 继承关系纯粹
    生成的实例既是子类的实例,也是父类的实例
  3. 可以通过子类直接访问父类原型链属性和函数
    通过原型链继承的子类,可以直接访问到父类原型链上新增的函数和属性

缺点

  1. 子类的所有实例将共享父类的属性
    在使用原型链继承时,如果父类有个属性的值为引用类型,那么改变Cat某个实例的属性值将会影响其他实例的属性值
  2. 在创建子类实现时,无法向父类的构造函数传递参数
    在通过new操作符创建子类的实例时,会调用子类的构造函数,而在子类的构造函数中并没有设置与父类的关联,从而导致无法向父类的构造函数传递参数
  3. 无法实现多继承
    由于子类prototype的属性只能设置为一个值,如果同时设置为多个值的话,后面的值会覆盖前面的值,导致Cat只能继承一个父类,而无法实现多继承
  4. 为子类增加原型对象上的属性和函数时,必须放在new Animal()后
    实现继承的关键语句是下面这句代码,它实现了子类prototype属性的改写。
Cat.prototype = new Animal();

如果想要为子类新增原型对象上的属性和函数,那么需要在这个语句之后添加。因为如果在这个语句之前设置了prototype属性,后面的语句会直接重写prototype属性,导致之前的设置全部失效

2.2. 借用构造函数继承(假的)

  1. 套路:
    1. 定义父类型构造函数
    2. 定义子类型构造函数
    3. 在子类型构造函数中调用父类型构造
  2. 关键:
    1. 在子类型构造函数中通用call()调用父类型构造函数
  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)

2.3.组合继承

  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 = new Student('Tom', 24, 15000)
  s.setName('Bob')
  s.setPrice(16000)
  console.log(s.name, s.age, s.price)

线程机制与事件机制

1.进程与线程

  1. 进程:程序的一次执行, 它占有一片独有的内存空间
  2. 线程: CPU的基本调度单位, 是程序执行的一个完整流程
    是进程内的一个独立执行单元
    是程序执行的一个完整流程
    是CPU的最小的调度单元
  3. 进程与线程
  • 一个进程中一般至少有一个运行的线程: 主线程
  • 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
  • 一个进程内的数据可以供其中的多个线程直接共享
  • 多个进程之间的数据是不能直接共享的
  1. 浏览器运行是单进程还是多进程?
  • 有的是单进程
    • firefox
    • 老版IE
  • 有的是多进程
    • chrome
    • 新版IE
  1. 如何查看浏览器是否是多进程运行的呢?
  • 任务管理器==>进程
  1. 浏览器运行是单线程还是多线程?
  • 都是多线程运行的

2.浏览器内核

  1. 什么是浏览器内核?
  • 支持浏览器运行的最核心的程序
  1. 不同的浏览器可能不太一样
  • Chrome, Safari: webkit
  • firefox: Gecko
  • IE: Trident
  • 360,搜狗等国内浏览器: Trident + webkit
  1. 内核由很多模块组成
  • html,css文档解析模块 : 负责页面文本的解析

  • dom/css模块 : 负责dom/css在内存中的相关处理

  • 布局和渲染模块 : 负责页面的布局和效果的绘制

  • 布局和渲染模块 : 负责页面的布局和效果的绘制

  • 定时器模块 : 负责定时器的管理

  • 网络请求模块 : 负责服务器请求(常规/Ajax)

  • 事件响应模块 : 负责事件的管理

JS是单线程还是多线程?
js是单线程运行的,但使用H5中的Web Workers可以多线程运行

3.定时器引发的思考

  1. 定时器真是定时执行的吗?
  • 定时器并不能保证真正定时执行
  • 一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)
  1. 定时器回调函数是在分线程执行的吗?
  • 在主线程执行的, js是单线程的
  1. 定时器是如何实现的?
  • 事件循环模型(后面讲)
  document.getElementById('btn').onclick = function () {
    var start = Date.now()
    console.log('启动定时器前...')
    setTimeout(function () {
      console.log('定时器执行了', Date.now()-start)
    }, 200)
    console.log('启动定时器后...')

    // 做一个长时间的工作
    for (var i = 0; i < 1000000000; i++) {

    }
  }

4.JS是单线程的

  1. 如何证明js执行是单线程的?
  • setTimeout()的回调函数是在主线程执行的
  • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行
  1. 为什么js要用单线程模式, 而不用多线程模式?
  • JavaScript的单线程,与它的用途有关。
  • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。
  • 这决定了它只能是单线程,否则会带来很复杂的同步问题
  1. 代码的分类:
  • 初始化代码
  • 回调代码
  1. js引擎执行代码的基本流程
  • 先执行初始化代码: 包含一些特别的代码 回调函数(异步执行)
    • 设置定时器
    • 绑定事件监听
    • 发送ajax请求
  • 后面在某个时刻才会执行回调代码
  setTimeout(function () {
    console.log('timeout 2222')
    alert('22222222')
  }, 2000)
  setTimeout(function () {
    console.log('timeout 1111')
    alert('1111111')
  }, 1000)
  setTimeout(function () {
    console.log('timeout() 00000')
  }, 0)
  function fn() {
    console.log('fn()')
  }
  fn()

  console.log('alert()之前')
  alert('------') //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
  console.log('alert()之后')

5.事件循环模型

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

6.H5 web Workers

  1. H5规范提供了js分线程的实现, 取名为: Web Workers
  2. 相关API
  • Worker: 构造函数, 加载分线程执行的js文件
  • Worker.prototype.onmessage: 用于接收另一个线程的回调函数
  • Worker.prototype.postMessage: 向另一个线程发送消息
  1. 不足
  • worker内代码不能操作DOM(更新UI)
  • 不能跨域加载JS
  • 不是每个浏览器都支持这个新特性
<body>
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
  var input = document.getElementById('number')
  document.getElementById('btn').onclick = function () {
    var number = input.value

    //创建一个Worker对象
    var worker = new Worker('worker.js')
    // 绑定接收消息的监听
    worker.onmessage = function (event) {
      console.log('主线程接收分线程返回的数据: '+event.data)
      alert(event.data)
    }

    // 向分线程发送消息
    worker.postMessage(number)
    console.log('主线程向分线程发送数据: '+number)
  }
  // console.log(this) // window

</script>
</body>
//worker.js
function fibonacci(n) {
  return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2)  //递归调用
}

console.log(this)
this.onmessage = function (event) {
  var number = event.data
  console.log('分线程接收到主线程发送的数据: '+number)
  //计算
  var result = fibonacci(number)
  postMessage(result)
  console.log('分线程向主线程返回数据: '+result)
  // alert(result)  alert是window的方法, 在分线程不能调用
  // 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
}

补充

1.分号问题

  1. js一条语句的后面可以不加分号
  2. 是否加分号是编码风格问题, 没有应该不应该,只有你自己喜欢不喜欢
  3. 在下面2种情况下不加分号会有问题
  • 小括号开头的前一条语句
  • 中方括号开头的前一条语句
  1. 解决办法: 在行首加分号
  2. 强有力的例子: vue.js库
  3. 知乎热议: https://www.zhihu.com/question/20298345
  // 情形一: 小括号开头的前一条语句
  var a = 3
  ;(function () {

  })
  /*
  错误理解: 将3看成是函数调用
   var a = 3(function () {

   })
   */

  // 情形二: 中方括号开头的前一条语句
  var b = a
  ;[1, 3, 5].forEach(function (item) {
    console.log(item)
  })
  /*
   错误理解:
   a = b[5].forEach(function(e){
   console.log(e)
   })
   
   */

2.内存溢出与内存泄漏

2.1. 内存溢出

  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误

2.2. 内存泄露

  • 占用的内存没有及时释放
  • 内存泄露积累多了就容易导致内存溢出
  • 常见的内存泄露:
    • 意外的全局变量
    • 没有及时清理的计时器或回调函数
    • 闭包
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值