原型与原型链

  • 所有函数都有一个特别的属性:
    • prototype : 显式原型属性
  • 所有实例对象都有一个特别的属性:
    • __proto__ : 隐式原型属性
  1. //定义构造函数
  2. function Fn() { } // 内部语句: this.prototype = {}
  3. // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
  4. console.log(Fn.prototype)
  5. // 2. 每个实例对象都有一个__proto__,可称为隐式原型
  6. //创建实例对象
  7. var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
  8. console.log(fn.__proto__)
  9. // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  10. console.log(Fn.prototype===fn.__proto__) // true
  11. //给原型添加方法
  12. Fn.prototype.test = function () {
  13. console.log('test()')
  14. }
  15. //通过实例调用原型的方法
  16. fn.test()

02_函数高级 - 图1

  1. 1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
  3. 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 4. 内存结构(图)
  5. 5. 总结:
  6. * 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  7. * 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  8. * 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
  • 显式原型与隐式原型的关系
    • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
    • 实例对象的__proto__: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 原型链
    • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
    • 这样通过__proto__属性就形成了一个链的结构——>原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

02_函数高级 - 图2

  1. /*
  2. 1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
  3. */
  4. console.log(Fn.prototype instanceof Object) // true
  5. console.log(Object.prototype instanceof Object) // false
  6. console.log(Function.prototype instanceof Object) // true
  7. /*
  8. 2. 所有函数都是Function的实例(包含Function)
  9. */
  10. console.log(Function.__proto__===Function.prototype) //true
  11. /*
  12. 3. Object的原型对象是原型链尽头
  13. */
  14. console.log(Object.prototype.__proto__) // null

04 原型链 属性问题

  1. 1. 读取对象的属性值时: 会自动到原型链中查找
  2. 2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  3. 3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
  1. function Fn() { }
  2. Fn.prototype.a = 'xxx'
  3. var fn1 = new Fn()
  4. console.log(fn1.a, fn1) //xxx
  5. var fn2 = new Fn()
  6. fn2.a = 'yyy'
  7. console.log(fn1.a, fn2.a, fn2) //xxx yyy

属性和方法都可以放到 对象上或者对象的原型上,读取的时候在对象和对象的原型上都会查找
但是一般定义的时候,我们习惯于属性定义在对象上,方法定义在对象的原型上

为什么?因为每个对象的方法是一样的,属性是不一样的。

05 探索instanceof

  1. 1. instanceof是如何判断的?
  2. * 表达式: A instanceof B
  3. * 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false
  4. * 这个A是实例对象,B是构造函数
  5. 2. Function是通过new自己产生的实例
  1. function Foo() { }
  2. var f1 = new Foo()
  3. console.log(f1 instanceof Foo) // true
  4. console.log(f1 instanceof Object) // true
  5. /*
  6. 案例2
  7. */
  8. console.log(Object instanceof Function) // true
  9. console.log(Object instanceof Object) // true
  10. console.log(Function instanceof Function) // true
  11. console.log(Function instanceof Object) // true
  12. function Foo() {}
  13. console.log(Object instanceof Foo) // false

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

01 变量提升与函数提升

1、 变量声明提升

  • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  • 值: undefined

2、函数声明提升

  • 通过function声明的函数, 在之前就可以直接调用
  • 值: 函数定义(对象)
  1. fn2() //可调用 函数提升
  2. // fn3() //不能 变量提升
  3. function fn2() {//这是function声明函数
  4. console.log('fn2()')
  5. }
  6. var fn3 = function () {//这是变量声明
  7. console.log('fn3()')
  8. }

3、先执行变量提升, 再执行函数提升,函数提升不会被同名变量声明时覆盖,但会被变量赋值后覆盖

  1. /*
  2. 测试题1: 先执行变量提升, 再执行函数提升
  3. */
  4. console.log(typeof a) // 'function'
  5. function a() {}
  6. var a
  7. console.log(typeof a) // 'function'
  8. /*
  9. 测试题2:
  10. */
  11. if (!(b in window)) {
  12. var b = 1
  13. }
  14. console.log(b) // undefined
  15. /*
  16. 测试题3:
  17. */
  18. var c = 1
  19. function c(c) {
  20. console.log(c)
  21. var c = 3
  22. }
  23. c(2) // 报错

02 执行上下文

  1. 1 代码分类(位置)
  2. * 全局代码
  3. * 函数(局部)代码
  4. 2 全局执行上下文
  5. * 在执行全局代码前将window确定为全局执行上下文
  6. * 对全局数据进行预处理
  7. * var定义的全局变量==>undefined, 添加为window的属性
  8. * function声明的全局函数==>赋值(fun), 添加为window的方法
  9. * this==>赋值(window)
  10. * 开始执行全局代码
  11. 3 函数执行上下文
  12. * 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  13. * 对局部数据进行预处理
  14. * 形参变量==>赋值(实参)==>添加为执行上下文的属性
  15. * arguments==>赋值(实参列表), 添加为执行上下文的属性
  16. * var定义的局部变量==>undefined, 添加为执行上下文的属性
  17. * function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
  18. * this==>赋值(调用函数的对象)
  19. * 开始执行函数体代码
  1. console.log(a1, window.a1)//undefined,undefined
  2. window.a2()//可以执行
  3. console.log(this)//window
  4. var a1 = 3
  5. function a2() {
  6. console.log('a2()')
  7. }
  8. console.log(a1)
  1. //函数执行上下文
  2. fn(1,2);
  3. function fn(b1){
  4. console.log(b1)//1
  5. console.log(b2)//undefined
  6. console.log(b3)//执行函数
  7. b3()
  8. console.log(this)//window
  9. console.log(arguments)//伪数组[1,2]
  10. var b2 =3
  11. function b3() {
  12. console.log('b3{}')
  13. }
  14. }

03 问题: 变量提升和函数提升是如何产生的?

  • 理解
    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:
    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期
    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:
    • 全局 :
      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数
      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组
  • 执行上下文创建和初始化的过程
    • 全局:
      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:
      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

作用域与作用域链

基本概念

  1. * 理解:
  2. * 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
  3. * 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
  4. * 分类:
  5. * 全局
  6. * 函数
  7. * js没有块作用域(在ES6之前)
  8. * 作用
  9. * 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
  10. * 作用域链: 查找变量
  11. * 区别作用域与执行上下文
  12. * 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
  13. * 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
  14. * 联系: 执行上下文环境是在对应的作用域中的

作用域与执行上下文

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

作用域链

  1. 1. 理解
  2. * 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  3. * 查找变量时就是沿着作用域链来查找的
  4. 2. 查找一个变量的查找规则
  5. * 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  6. * 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  7. * 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

面试题1

  1. /*作用域是静态的,不会说放在函数里执行就改变作用域的位置
  2. * 换句话说,作用域是一开始就定好的
  3. * */
  4. let x = 10;
  5. function fn() {
  6. console.log(x); //输出10
  7. }
  8. function show(f) {
  9. let x = 20;
  10. f();
  11. }
  12. show(fn);

面试题2

  1. <script type="text/javascript">
  2. let fn = function () {
  3. console.log(fn)
  4. }
  5. fn()
  6. let obj = {
  7. fn2: function () {
  8. console.log(fn2) //报错,fn是变量,fn2是属性
  9. console.log(this.fn2) //可以输出函数
  10. }
  11. }
  12. obj.fn2()
  13. </script>

闭包

00 基本概念

  1. * 理解:
  2. * 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
  3. * 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  4. * 作用:
  5. * 延长局部变量的生命周期
  6. * 让函数外部能操作内部的局部变量
  7. * 写一个闭包程序

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

  1. * 闭包应用:
  2. * 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
  3. * 循环遍历加监听
  4. * JS框架(jQuery)大量使用了闭包
  5. * 缺点:
  6. * 变量占用内存的时间可能会过长
  7. * 可能导致内存泄露
  8. * 解决:
  9. * 及时释放 : f = null; //让内部函数对象成为垃圾对象

01 理解闭包

  1. 1. 如何产生闭包?
  2. * 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  3. * 执行函数定义就会产生闭包(不用调用内部函数)
  4. 2. 闭包到底是什么?
  5. * 使用chrome调试查看
  6. * 理解一: 闭包是嵌套的内部函数(绝大部分人)
  7. * 理解二: 包含被引用变量(函数)的对象(极少数人)
  8. * 注意: 闭包存在于嵌套的内部函数中
  9. 3. 产生闭包的条件?
  10. * 函数嵌套
  11. * 内部函数引用了外部函数的数据(变量/函数)

02 常见的闭包

  1. // 1. 将函数作为另一个函数的返回值
  2. function fn1() {
  3. let a = 2
  4. function fn2() {
  5. a++
  6. console.log(a)
  7. }
  8. return fn2
  9. }
  10. fn1()()//3
  11. fn1()()//3
  12. fn1()()//3
  13. let f = fn1()
  14. f()//3
  15. f()//4
  16. f()//5

03 闭包的作用

  1. 1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  2. 2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
  3. 问题:
  4. 1. 函数执行完后, 函数内部声明的局部变量是否还存在? 一般是不存在, 存在于闭中的变量才可能存在
  5. 2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它

04 闭包的生命周期

  1. 1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
  2. 2. 死亡: 在嵌套的内部函数成为垃圾对象时
  3. <script type="text/javascript">
  4. function fn1() {
  5. //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
  6. let a = 2
  7. function fn2 () {
  8. a++
  9. console.log(a)
  10. }
  11. return fn2
  12. }
  13. let f = fn1()
  14. f() // 3
  15. f() // 4
  16. f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
  17. </script>

05 闭包的应用: 定义JS模块

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

自定义JS模块一

通过函数的return

  1. function myModule() {
  2. //私有数据
  3. let msg = 'Hello JavaScript'
  4. //操作数据的函数
  5. function doSomething() {
  6. console.log('doSomething() ' + msg.toUpperCase())
  7. }
  8. function doOtherthing() {
  9. console.log('doOtherthing() ' + msg.toLowerCase())
  10. }
  11. //向外暴露对象(给外部使用的方法)
  12. return {
  13. doSomething: doSomething,
  14. doOtherthing: doOtherthing
  15. }
  16. }
  1. <script type="text/javascript" src="myModule.js"></script>
  2. <script type="text/javascript">
  3. let module = myModule()
  4. module.doSomething()
  5. module.doOtherthing()
  6. </script>

自定义JS模块二

匿名函数自调用,将对象添加到window中

(实际开发中肯定不允许这么做,怎么能随便改变window的属性呢?我猜的)

  1. (function (window) {
  2. //私有数据
  3. let msg = 'Hello JavaSript'
  4. //操作数据的函数
  5. function doSomething() {
  6. console.log('doSomething() ' + msg.toUpperCase())
  7. }
  8. function doOtherthing() {
  9. console.log('doOtherthing() ' + msg.toLowerCase())
  10. }
  11. //向外暴露对象(给外部使用的方法)
  12. window.myModule2 = {
  13. doSomething: doSomething,
  14. doOtherthing: doOtherthing
  15. }
  16. })(window)
  17. /*函数添加window参数,是为了代码压缩*/
  1. <script type="text/javascript" src="myModule2.js"></script>
  2. <script type="text/javascript">
  3. myModule2.doSomething()
  4. myModule2.doOtherthing()
  5. </script>

06 闭包的缺点

  1. 1. 缺点
  2. * 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  3. * 容易造成内存泄露
  4. 2. 解决
  5. * 能不用闭包就不用
  6. * 及时释放
  1. <script type="text/javascript">
  2. function fn1() {
  3. let arr = new Array(100000)
  4. function fn2() {
  5. console.log(arr.length)
  6. }
  7. return fn2
  8. }
  9. let f = fn1()
  10. f()
  11. f = null //让内部函数成为垃圾对象-->回收闭包
  12. </script>

07 面试题1

  1. <script type="text/javascript">
  2. //代码片段一
  3. let name = "The Window";
  4. let object = {
  5. name: "My Object",
  6. getNameFunc: function () {
  7. return function () {
  8. return this.name;
  9. };
  10. }
  11. };
  12. alert(object.getNameFunc()()); //? the window
  13. //object.getNameFunc()是object执行的,此时this是指向object的,并且它返回了一个函数
  14. //object.getNameFunc()()直接执行了返回的函数
  15. //这里没有闭包
  16. //代码片段二
  17. let name2 = "The Window";
  18. let object2 = {
  19. name2: "My Object",
  20. getNameFunc: function () {
  21. let that = this;
  22. return function () {
  23. return that.name2;
  24. };
  25. }
  26. };
  27. alert(object2.getNameFunc()()); //? my object
  28. //这里有闭包!
  29. </script>

07 面试题2

  1. <script type="text/javascript">
  2. function fun(n, o) {
  3. console.log(o)
  4. return {
  5. fun: function (m) {
  6. return fun(m, n)
  7. }
  8. }
  9. }
  10. let a = fun(0)
  11. a.fun(1)
  12. a.fun(2)
  13. a.fun(3)//undefined,0,0,0
  14. let b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
  15. let c = fun(0).fun(1)
  16. c.fun(2)
  17. c.fun(3)//undefined,0,1,1
  18. </script>

内存溢出与内存泄露

00 基本概念

  1. 1. 内存溢出
  2. * 一种程序运行出现的错误
  3. * 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  4. 2. 内存泄露
  5. * 占用的内存没有及时释放
  6. * 内存泄露积累多了就容易导致内存溢出
  7. * 常见的内存泄露:
  8. * 意外的全局变量
  9. * 没有及时清理的计时器或回调函数
  10. * 闭包
  1. /*
  2. 1. 内存溢出
  3. */
  4. let obj = {}
  5. for (let i = 0; i < 1000; i++) {
  6. obj[i] = new Array(1000000000);
  7. //这里obj[i] 是伪数组,obj里面会有属性名为0、1、2、3...999
  8. }
  9. console.log(obj)
  1. /*
  2. 2. 内存泄露
  3. */
  4. // ①意外的全局变量
  5. function fn() {
  6. a = new Array(10000000) //这里没有写var、let来定义变量,所以这是向上找a,找不到,就在window中添加a属性并赋值了。
  7. console.log(a)
  8. }
  9. fn()
  10. // ②没有及时清理的计时器或回调函数
  11. let intervalId = setInterval(function () { //启动循环定时器后不清理
  12. console.log('----')
  13. }, 1000)
  14. // clearInterval(intervalId)
  15. // ③闭包