1.原型与原型链

1.原型(prototype)

  1. 函数的prototype属性(图)
  • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  • 原型对象中有一个属性constructor, 它指向函数对象
  1. 给原型对象添加属性(一般都是方法)
  • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)
  1. // 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
  2. console.log(Date.prototype, typeof Date.prototype)
  3. function Fun () {//alt + shift +r(重命名rename)
  4. }
  5. console.log(Fun.prototype) // 默认指向一个Object空对象(没有我们的属性)
  6. // 原型对象中有一个属性constructor, 它指向函数对象
  7. console.log(Date.prototype.constructor===Date)
  8. console.log(Fun.prototype.constructor===Fun)
  9. //给原型对象添加属性(一般是方法) ===>实例对象可以访问
  10. Fun.prototype.test = function () {
  11. console.log('test()')
  12. }
  13. var fun = new Fun()
  14. fun.test()

2.显示原型与隐示原型

  1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 每个实例对象都有一个proto,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 内存结构(图)
  1. 总结:
  • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  • 对象的proto属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
  1. //定义构造函数
  2. function Fn() { // 内部语句: this.prototype = {}
  3. }
  4. // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
  5. console.log(Fn.prototype)
  6. // 2. 每个实例对象都有一个__proto__,可称为隐式原型
  7. //创建实例对象
  8. var fn = new Fn() // 内部语句: this.__proto__ = Fn.prototype
  9. console.log(fn.__proto__)
  10. // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  11. console.log(Fn.prototype===fn.__proto__) // true
  12. //给原型添加方法
  13. Fn.prototype.test = function () {
  14. console.log('test()')
  15. }
  16. //通过实例调用原型的方法
  17. fn.test()

3.原型链

  1. 原型链(图解)
  • 访问一个对象的属性时,
    • 先在自身属性中查找,找到返回
    • 如果没有, 再沿着proto这条链向上查找, 找到返回
    • 如果最终没找到, 返回undefined
  • 别名: 隐式原型链
  • 作用: 查找对象的属性(方法)
  1. 构造函数/原型/实体对象的关系(图解)
  2. 构造函数/原型/实体对象的关系2(图解)
  1. // console.log(Object)
  2. //console.log(Object.prototype)
  3. console.log(Object.prototype.__proto__)
  4. function Fn() {
  5. this.test1 = function () {
  6. console.log('test1()')
  7. }
  8. }
  9. console.log(Fn.prototype)
  10. Fn.prototype.test2 = function () {
  11. console.log('test2()')
  12. }
  13. var fn = new Fn()
  14. fn.test1()
  15. fn.test2()
  16. console.log(fn.toString())
  17. console.log(fn.test3)
  18. // fn.test3()
  19. /*
  20. 1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)
  21. */
  22. console.log(Fn.prototype instanceof Object) // true
  23. console.log(Object.prototype instanceof Object) // false
  24. console.log(Function.prototype instanceof Object) // true
  25. /*
  26. 2. 所有函数都是Function的实例(包含Function)
  27. */
  28. console.log(Function.__proto__===Function.prototype)
  29. /*
  30. 3. Object的原型对象是原型链尽头
  31. */
  32. console.log(Object.prototype.__proto__) // null

4,原型链属性问题

  1. 读取对象的属性值时: 会自动到原型链中查找
  2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
  1. unction Fn() {
  2. }
  3. Fn.prototype.a = 'xxx'
  4. var fn1 = new Fn()
  5. console.log(fn1.a, fn1)
  6. var fn2 = new Fn()
  7. fn2.a = 'yyy'
  8. console.log(fn1.a, fn2.a, fn2)
  9. function Person(name, age) {
  10. this.name = name
  11. this.age = age
  12. }
  13. Person.prototype.setName = function (name) {
  14. this.name = name
  15. }
  16. var p1 = new Person('Tom', 12)
  17. p1.setName('Bob')
  18. console.log(p1)
  19. var p2 = new Person('Jack', 12)
  20. p2.setName('Cat')
  21. console.log(p2)
  22. console.log(p1.__proto__===p2.__proto__) // true

5.探索instanceof

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

6.面试题

  1. /*
  2. 测试题1
  3. */
  4. function A () {
  5. }
  6. A.prototype.n = 1
  7. var b = new A()
  8. A.prototype = {
  9. n: 2,
  10. m: 3
  11. }
  12. var c = new A()
  13. console.log(b.n, b.m, c.n, c.m)
  14. /*
  15. 测试题2
  16. */
  17. function F (){}
  18. Object.prototype.a = function(){
  19. console.log('a()')
  20. }
  21. Function.prototype.b = function(){
  22. console.log('b()')
  23. }
  24. var f = new F()
  25. f.a()
  26. // f.b()
  27. F.a()
  28. F.b()
  29. console.log(f)
  30. console.log(Object.prototype)
  31. console.log(Function.prototype)

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

1.变量提升与函数提升

  1. 变量声明提升
  • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  • 值: undefined
  1. 函数声明提升
  • 通过function声明的函数, 在之前就可以直接调用
  • 值: 函数定义(对象)
  1. 问题: 变量提升和函数提升是如何产生的?
  1. /*
  2. 面试题 : 输出 undefined
  3. */
  4. var a = 3
  5. function fn () {
  6. console.log(a)
  7. var a = 4
  8. }
  9. fn()
  10. console.log(b) //undefined 变量提升
  11. fn2() //可调用 函数提升
  12. // fn3() //不能 变量提升
  13. var b = 3
  14. function fn2() {
  15. console.log('fn2()')
  16. }
  17. var fn3 = function () {
  18. console.log('fn3()')
  19. }

2.执行上下文

  1. 代码分类(位置)
  • 全局代码
  • 函数(局部)代码
  1. 全局执行上下文
  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
    • var定义的全局变量==>undefined, 添加为window的属性
    • function声明的全局函数==>赋值(fun), 添加为window的方法
    • this==>赋值(window)
  • 开始执行全局代码
  1. 函数执行上下文
  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  • 对局部数据进行预处理
    • 形参变量==>赋值(实参)==>添加为执行上下文的属性
    • arguments==>赋值(实参列表), 添加为执行上下文的属性
    • var定义的局部变量==>undefined, 添加为执行上下文的属性
    • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
    • this==>赋值(调用函数的对象)
  • 开始执行函数体代码
  1. console.log(a1, window.a1)
  2. window.a2()
  3. console.log(this)
  4. var a1 = 3
  5. function a2() {
  6. console.log('a2()')
  7. }
  8. console.log(a1)

3.执行上下文栈

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

4.面试题

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

3.作用域与作用域链

1.作用域

  1. 理解
  • 就是一块”地盘”, 一个代码段所在的区域
  • 它是静态的(相对于上下文对象), 在编写代码时就确定了
  1. 分类
  • 全局作用域
  • 函数作用域
  • 没有块作用域(ES6有了)
  1. 作用
  • 隔离变量,不同作用域下同名变量不会有冲突
  1. /* //没块作用域
  2. if(true) {
  3. var c = 3
  4. }
  5. console.log(c)*/
  6. var a = 10,
  7. b = 20
  8. function fn(x) {
  9. var a = 100,
  10. c = 300;
  11. console.log('fn()', a, b, c, x)
  12. function bar(x) {
  13. var a = 1000,
  14. d = 400
  15. console.log('bar()', a, b, c, d, x)
  16. }
  17. bar(100)
  18. bar(200)
  19. }
  20. fn(10)

2.作用域与执行上下文

  1. 区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建
  1. 区别2
  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
  1. 联系
  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域
  1. var a = 10,
  2. b = 20
  3. function fn(x) {
  4. var a = 100,
  5. c = 300;
  6. console.log('fn()', a, b, c, x)
  7. function bar(x) {
  8. var a = 1000,
  9. d = 400
  10. console.log('bar()', a, b, c, d, x)
  11. }
  12. bar(100)
  13. bar(200)
  14. }
  15. fn(10)

3.作用域链

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

4.闭包

1.理解闭包

  1. 如何产生闭包?
  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  1. 闭包到底是什么?
  • 使用chrome调试查看
  • 理解一: 闭包是嵌套的内部函数(绝大部分人)
  • 理解二: 包含被引用变量(函数)的对象(极少数人)
  • 注意: 闭包存在于嵌套的内部函数中
  1. 产生闭包的条件?
  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)
  1. function fn1 () {
  2. var a = 2
  3. var b = 'abc'
  4. function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
  5. console.log(a)
  6. }
  7. // fn2()
  8. }
  9. fn1()
  10. function fun1() {
  11. var a = 3
  12. var fun2 = function () {
  13. console.log(a)
  14. }
  15. }
  16. fun1()

2.常见的闭包

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

3.闭包的作用

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

问题:

  1. 1. 函数执行完后, 函数内部声明的局部变量是否还存在? 一般是不存在, 存在于闭中的变量才可能存在
  2. 2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它
  1. function fn1() {
  2. var a = 2
  3. function fn2() {
  4. a++
  5. console.log(a)
  6. // return a
  7. }
  8. function fn3() {
  9. a--
  10. console.log(a)
  11. }
  12. return fn3
  13. }
  14. var f = fn1()
  15. f() // 1
  16. f() // 0

4.闭包的生命周期

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

5.闭包的应用

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

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
  1. 方式一:通过返回对象
  2. function myModule() {
  3. //私有数据
  4. var msg = 'My atguigu'
  5. //操作数据的函数
  6. function doSomething() {
  7. console.log('doSomething() '+msg.toUpperCase())
  8. }
  9. function doOtherthing () {
  10. console.log('doOtherthing() '+msg.toLowerCase())
  11. }
  12. //向外暴露对象(给外部使用的方法)
  13. return {
  14. doSomething: doSomething,
  15. doOtherthing: doOtherthing
  16. }
  17. }
  18. -------------------------------------------- HTML页面 ----------------------------------------------
  19. <script type="text/javascript" src="myModule.js"></script>
  20. <script type="text/javascript">
  21. var module = myModule()
  22. module.doSomething()
  23. module.doOtherthing()
  24. </script>
  1. 方式二:通过立即执行函数,把属性赋值给全局变量
  2. (function () {
  3. //私有数据
  4. var msg = 'My atguigu'
  5. //操作数据的函数
  6. function doSomething() {
  7. console.log('doSomething() '+msg.toUpperCase())
  8. }
  9. function doOtherthing () {
  10. console.log('doOtherthing() '+msg.toLowerCase())
  11. }
  12. //向外暴露对象(给外部使用的方法)
  13. window.myModule2 = {
  14. doSomething: doSomething,
  15. doOtherthing: doOtherthing
  16. }
  17. })()
  18. ------------------------------------------------- HTML页面 ----------------------------------------------
  19. <script type="text/javascript" src="myModule2.js"></script>
  20. <script type="text/javascript">
  21. myModule2.doSomething()
  22. myModule2.doOtherthing()

6.闭包的缺点及解决

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

7.面试题

  1. //代码片段一
  2. var name = "The Window";
  3. var object = {
  4. name : "My Object",
  5. getNameFunc : function(){
  6. return function(){
  7. return this.name;
  8. };
  9. }
  10. };
  11. alert(object.getNameFunc()()); //? the window
  12. //代码片段二
  13. var name2 = "The Window";
  14. var object2 = {
  15. name2 : "My Object",
  16. getNameFunc : function(){
  17. var that = this;
  18. return function(){
  19. return that.name2;
  20. };
  21. }
  22. };
  23. alert(object2.getNameFunc()()); //? my object
  1. //终极闭包面试题
  2. function fun(n,o) {
  3. console.log(o)
  4. return {
  5. fun:function(m){
  6. return fun(m,n)
  7. }
  8. }
  9. }
  10. var a = fun(0)
  11. a.fun(1)
  12. a.fun(2)
  13. a.fun(3)//undefined,0,0,0
  14. var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
  15. var c = fun(0).fun(1)
  16. c.fun(2)
  17. c.fun(3)//undefined,0,1,1