原型与原型链 原型(prototype) 函数的prototype属性 每个函数都有一个prototype
属性,它默认指向一个Object空对象(即为原型对象)
原型对象中有一个属性constructor
,它指向函数对象
给原型对象添加属性(一般都是方法) 函数的所有实例对象自动拥有原型中的属性(方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > console .log (Date .prototype , typeof Date .prototype ) function Fun ( ) { } console .log (Fun .prototype ) console .log (Date .prototype .constructor === Date ) console .log (Fun .prototype .constructor === Fun ) Fun .prototype .test = function ( ) { console .log ('成功啦' ) } let fun = new Fun () fun.test () </script >
显示原型与隐式原型 每个函数function 都有一个prototype ,也就是显示原型属性,默认指向一个空的Object对象
每个实例对象都有一个__proto__
,也就是隐式原型
对象的隐式原型的值为其对应构造函数的显示原型的值
总结
内存结构图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <script > function A ( ) { } console .log (A.prototype ) let a = new A () console .log (a.__proto__ ) console .log (A.prototype === a.__proto__ ) A.prototype .demo = function ( ){ alert ("恭喜成功啦" ) } a.demo () </script >
原型链 原型链(别名:隐式原型链)
访问一个对象的属性时,
先在自身属性中查找,找到返回
如果没有,在沿着__proto__
这条链向上查找,找到返回
如果最终还没找到,返回undefined
原型链,又称隐式原型链
作用:查找对象的属性(方法)
原型链图解
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 34 35 <script > console .log (Object .prototype .__proto__ ) function Fn ( ) { this .test1 = function ( ) { console .log (`test1()成功` ) } } console .log (Fn .prototype ) Fn .prototype .test2 = function ( ) { console .log (`test2()成功` ) } let fn = new Fn () fn.test1 () fn.test2 () console .log (fn.toString ()) console .log (fn.test3 ) console .log (Fn .prototype instanceof Object ) console .log (Object .prototype instanceof Object ) console .log (Function .prototype instanceof Object ) console .log (Function .__proto__ === Function .prototype ) console .log (Object .prototype .__proto__ ) </script >
原型链属性问题 读取对象的属性值时,会自动到原型链中查找
设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值
方法一般定义在原型中,属性一般通过构造函数定义在对象本身上
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 <script > function Fn ( ) { } Fn .prototype .a = `xxx` let fn1 = new Fn () console .log (fn1.a , fn1) let fn2 = new Fn () fn2.a = `yyy` console .log (fn1.a , fn2.a , fn2) function Person (name, age ) { this .name = name this .age = age } Person .prototype .setName = function (name ) { this .name = name } let p1 = new Person (`Tom` , 12 ) p1.setName (`libai` ) let p2 = new Person (`jack` , 12 ) p2.setName (`nice` ) console .log (p1) console .log (p2) console .log (p1.__proto__ === p2.__proto__ ) </script >
探索instanceof instanceof是如何判断的 表达式:A instanceof B 如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false
Function是通过new自己产生的实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script > function Foo ( ) { } let f1 = new Foo () console .log (f1 instanceof Foo ) console .log (f1 instanceof Object ) console .log (Object instanceof Function ) console .log (Object instanceof Object ) console .log (Function instanceof Function ) console .log (Function instanceof Object ) function Foo ( ) { } console .log (Object instanceof Foo ) </script >
面试题
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 34 35 <script > function A ( ){} A.prototype .n = 1 ; var b = new A (); A.prototype = { n :2 , m :3 }; var c = new A (); console .log (b.n ,b.m ,c.n ,c.m ); function F ( ){} Object .prototype .a = function ( ){ console .log ("a()" ); }; Function .prototype .b = function ( ){ console .log ("b()" ); }; var f = new F (); f.a (); F.a (); F.b (); console .log (f); console .log (Object .prototype ); console .log (Function .prototype ); </script >
执行上下文与执行上下文栈 变量提升与函数提升 变量的声明提升 通过var定义(声明)的变量,在定义语句之前就可以访问到,值为:undefined 函数声明提升 通过function声明的函数,在之前就可以直接调用,值为:函数定义(对象) 变量提升和函数提升是如何产生的
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 <script > var a = 3 ; function fn ( ){ console .log (a); var a = 4 ; } fn (); console .log (b); fn2 (); var b = 3 ; function fn2 ( ){ console .log ("fn2()" ); } var fn3 = function ( ){ console .log ("fn3()" ); }; </script >
执行上下文 代码分类(位置) 全局代码 函数(局部)代码 全局执行上下文 在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
var 定义的全局变量–>undefined,添加为window的属性 function声明的全局函数–>赋值(fun),添加为window的方法 this–>赋值(window) 开始执行全局代码 函数执行上下文 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在于栈中)
对局部数据进行预处理
形参变量–>赋值(实参)–>添加为执行上下文的属性 arguments–>赋值(实参列表),添加为执行上下文属性 var 定义的局部变量–>undefined,添加为执行上下文的属性 function声明的函数–>赋值(fun),添加为执行上下文的方法 开始执行函数体代码 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 <script > console .log (a1, window .a1 ) a2 () console .log (this ) var a1 = 3 function a2 ( ) { console .log ("a2()" ) } console .log (a1) console .log ("=========" ) function fn (a1 ) { console .log (a1) console .log (a2) a3 () console .log (this ) console .log (arguments ) var a2 = 3 function a3 ( ) { console .log ("a3()" ) } } fn (2 , 3 ) </script >
执行上下文栈 1.在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象 2.在全局执行上下文(window)确定后,将其添加到栈中(压栈) 3.在函数执行上下文创建后,将其添加到栈中(压栈) 4.在当前函数执行完后,将栈顶的对象移除(出栈) 5.当所有的代码执行完之后,栈中只剩下window
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <script > 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 ) </script >
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 34 35 36 <script > 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) </script >
面试题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > function a ( ) { } var a console .log (typeof a) if (!(b in window )) { var b = 1 } console .log (b) var c = 1 function c (c ) { console .log (c) var c = 3 } c (2 ) </script >
作用域与作用域链 作用域 定义:指一块空间,代码所在的区域,它是静态的(相对于上下文对象),在编写代码时就确定了
分类:
作用:隔离变量,不同作用域下,同名变量不会有冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 if (true ) { var c = 3 } console .log (c)var a = 10 var b = 20 function fn (x ) { var a = 100 c = 300 console .log ("fn()" , a, b, c, x) function bar (x ) { var a = 1000 var d = 400 console .log ("bar()" , a, b, c, d, x) } bar (100 ) bar (200 ) } fn (10 )
作用域与执行上下文 作用域与执行上下文的区别
区别1
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创建 函数执行上下文是在调用函数时,函数体代码执行之前创建 区别2
作用域是静态的,只要函数定义好了,就一直存在,且不会再变化 执行上下文是动态的,调用函数时创建,函数调用结束时就会自动释放 联系
上下文环境(对象)是从属于所在的作用域 全局上下文环境–>全局作用域 函数上下文环境–>对应的函数作用域 作用域链 定义:多个上下级关系的作用域形成的链,它的方向是从下向上的(从内到外),查找变量时就是沿着作用域链来查找的
查找一个变量的查找规则
在当前作用域下的执行上下文中查找对应的属性,如果有直接返回,否则进入2 在上一级作用域的执行上下文中查找对应的属性,如果有直接返回,否则进入3 再次执行2的相同操作,知道全局作用域,如果还找不到就抛出找不到的异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script > 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 () </script >
面试题 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 <script > var x = 10 function fn ( ) { console .log (x) } function show (f ) { var x = 20 f () } show (fn) console .log ("=========" ) var fn = function ( ) { console .log (fn) } fn () var obj = { fn2 : function ( ) { console .log (fn2) } } obj.fn2 () </script >
闭包 理解闭包 如何产生闭包 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
闭包到底是什么 理解一:闭包是嵌套的内部函数
理解二:包含被引用变量(函数)的对象
注:闭包存在于嵌套的内部函数中
产生闭包的条件 函数嵌套 内部函数引用了外部函数的数据(变量/函数)
1 2 3 4 5 6 7 8 9 10 11 12 <script > function fn1 ( ) { var a = 2 var b = "abc" function fn2 ( ) { console .log (a) } fn2 () } fn1 () </script >
常见的闭包 将函数作为另一个函数的返回值 将函数作为实参传递给另一个函数调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script > function fn1 ( ) { var a = 2 function fn2 ( ) { a++ console .log (a) } return fn2 } var f = fn1 () f () f () f = null function showDelay (msg, time ) { setTimeout (function ( ) { alert (msg) }, time) } showDelay ("青年大学习" , 2000 ) </script >
闭包的作用 作用 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期) 让函数外部可以操作(读写)到函数内部的数据(变量/函数) 问题 1.函数执行完后,函数内部声明的局部变量是否还存在?
一般是不存在的,存在于闭包的变量才可能存在
2.在函数外部能直接访问函数内部的局部变量么?
不能,但我们可以通过闭包让外部操作它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > function fn1 ( ) { var a = 2 function fn2 ( ) { a++ console .log (a) } function fn3 ( ) { a-- console .log (a) } return fn3 } var f = fn1 () f () f () f = null </script >
闭包的生命周期 产生:在嵌套内部函数定义执行完时就产生了(不是在调用)
死亡:在嵌套的内部函数成为垃圾对象时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script > function fn1 ( ){ var a = 2 function fn2 ( ){ a++ console .log (a) } return fn2 } var f = fn1 () f () f () f = null </script >
闭包的应用_自定义JS模块 自定义JS模块
具有特定功能的js文件 将所有的数据和功能都封装在一个函数内部(私有的) 只向外暴露一个包,执行n个方法的对象或函数 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能 1 2 3 4 5 6 <script src ="myModule2.js" > </script > <script > myModule2.doSomething() myModule2.doOtherthing() </script >
myModule2.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 (function ( ) { var msg = "My Class" 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 12 <script > function fn1 ( ) { var arr = new Array [100000 ] function fn2 ( ) { console .log (arr.length ) } return fn2 } var f = fn1 () f () f = null </script >
内存溢出与内存泄漏 内存溢出 一种程序运行出现的错误 当程序运行需要的内存超过了剩余内存时,就会抛出内存溢出的错误 内存泄漏 占用的内存没有及时释放 内存泄漏积累多了就容易导致内存溢出 常见的内存泄漏 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 34 35 36 37 38 39 <script > var obj = {} for (var i = 0 ; i < 10000 ; i++) { obj[i] = new Array (1000000 ) console .log ("-----" ) } function fn ( ) { a = new Array (1000000 ) console .log (a) } fn () var start = setInterval (function ( ) { console .log ("---" ) }, 2000 ) clearInterval (start) function fn1 ( ) { var a = 4 function fn2 ( ) { console .log (++a) } return fn2 } var f = fn1 () f () </script >
面试题 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 <script > var name = "这是Window" var object = { name : "这是Object" , getNameFunc : function ( ) { return function ( ) { return this .name } } } alert (object.getNameFunc ()()) var name2 = "这是Window" var object2 = { name2 : "这是Object" , getNameFunc : function ( ) { var that = this return function ( ) { return that.name2 } } } alert (object2.getNameFunc ()()) </script >