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

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

/*1. 函数的显示原型指向的对象默认是空Object实例对象(但Object不满足)*/console.log(Fn.prototype instanceof Object) // trueconsole.log(Object.prototype instanceof Object) // falseconsole.log(Function.prototype instanceof Object) // true/*2. 所有函数都是Function的实例(包含Function)*/console.log(Function.__proto__===Function.prototype) //true/*3. Object的原型对象是原型链尽头*/console.log(Object.prototype.__proto__) // null
04 原型链 属性问题
1. 读取对象的属性值时: 会自动到原型链中查找2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
function Fn() { }Fn.prototype.a = 'xxx'var fn1 = new Fn()console.log(fn1.a, fn1) //xxxvar fn2 = new Fn()fn2.a = 'yyy'console.log(fn1.a, fn2.a, fn2) //xxx yyy
属性和方法都可以放到 对象上或者对象的原型上,读取的时候在对象和对象的原型上都会查找
但是一般定义的时候,我们习惯于属性定义在对象上,方法定义在对象的原型上
为什么?因为每个对象的方法是一样的,属性是不一样的。
05 探索instanceof
1. instanceof是如何判断的?* 表达式: A instanceof B* 如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false* 这个A是实例对象,B是构造函数2. Function是通过new自己产生的实例
function Foo() { }var f1 = new Foo()console.log(f1 instanceof Foo) // trueconsole.log(f1 instanceof Object) // true/*案例2*/console.log(Object instanceof Function) // trueconsole.log(Object instanceof Object) // trueconsole.log(Function instanceof Function) // trueconsole.log(Function instanceof Object) // truefunction Foo() {}console.log(Object instanceof Foo) // false
执行上下文与执行上下文栈
01 变量提升与函数提升
1、 变量声明提升
- 通过var定义(声明)的变量, 在定义语句之前就可以访问到
- 值: undefined
2、函数声明提升
- 通过function声明的函数, 在之前就可以直接调用
- 值: 函数定义(对象)
fn2() //可调用 函数提升// fn3() //不能 变量提升function fn2() {//这是function声明函数console.log('fn2()')}var fn3 = function () {//这是变量声明console.log('fn3()')}
3、先执行变量提升, 再执行函数提升,函数提升不会被同名变量声明时覆盖,但会被变量赋值后覆盖
/*测试题1: 先执行变量提升, 再执行函数提升*/console.log(typeof a) // 'function'function a() {}var aconsole.log(typeof a) // 'function'/*测试题2:*/if (!(b in window)) {var b = 1}console.log(b) // undefined/*测试题3:*/var c = 1function c(c) {console.log(c)var c = 3}c(2) // 报错
02 执行上下文
1、 代码分类(位置)* 全局代码* 函数(局部)代码2、 全局执行上下文* 在执行全局代码前将window确定为全局执行上下文* 对全局数据进行预处理* var定义的全局变量==>undefined, 添加为window的属性* function声明的全局函数==>赋值(fun), 添加为window的方法* this==>赋值(window)* 开始执行全局代码3、 函数执行上下文* 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)* 对局部数据进行预处理* 形参变量==>赋值(实参)==>添加为执行上下文的属性* arguments==>赋值(实参列表), 添加为执行上下文的属性* var定义的局部变量==>undefined, 添加为执行上下文的属性* function声明的函数 ==>赋值(fun), 添加为执行上下文的方法* this==>赋值(调用函数的对象)* 开始执行函数体代码
console.log(a1, window.a1)//undefined,undefinedwindow.a2()//可以执行console.log(this)//windowvar a1 = 3function a2() {console.log('a2()')}console.log(a1)
//函数执行上下文fn(1,2);function fn(b1){console.log(b1)//1console.log(b2)//undefinedconsole.log(b3)//执行函数b3()console.log(this)//windowconsole.log(arguments)//伪数组[1,2]var b2 =3function b3() {console.log('b3{}')}}
03 问题: 变量提升和函数提升是如何产生的?
- 理解
- 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
- 执行上下文栈: 用来管理产生的多个执行上下文
- 分类:
- 全局: window
- 函数: 对程序员来说是透明的
- 生命周期
- 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
- 函数 : 调用函数时产生, 函数执行完时死亡
- 包含哪些属性:
- 全局 :
- 用var定义的全局变量 ==>undefined
- 使用function声明的函数 ===>function
- this ===>window
- 函数
- 用var定义的局部变量 ==>undefined
- 使用function声明的函数 ===>function
- this ===> 调用函数的对象, 如果没有指定就是window
- 形参变量 ===>对应实参值
- arguments ===>实参列表的伪数组
- 全局 :
- 执行上下文创建和初始化的过程
- 全局:
- 在全局代码执行前最先创建一个全局执行上下文(window)
- 收集一些全局变量, 并初始化
- 将这些变量设置为window的属性
- 函数:
- 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
- 收集一些局部变量, 并初始化
- 将这些变量设置为执行上下文的属性
- 全局:
作用域与作用域链
基本概念
* 理解:* 作用域: 一块代码区域, 在编码时就确定了, 不会再变化* 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量* 分类:* 全局* 函数* js没有块作用域(在ES6之前)* 作用* 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突* 作用域链: 查找变量* 区别作用域与执行上下文* 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了* 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失* 联系: 执行上下文环境是在对应的作用域中的
作用域与执行上下文
区别1* 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时* 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建* 函数执行上下文是在调用函数时, 函数体代码执行之前创建区别2* 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化* 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放联系* 执行上下文(对象)是从属于所在的作用域* 全局上下文环境==>全局作用域* 函数上下文环境==>对应的函数使用域
作用域链
1. 理解* 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)* 查找变量时就是沿着作用域链来查找的2. 查找一个变量的查找规则* 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2* 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3* 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
面试题1
/*作用域是静态的,不会说放在函数里执行就改变作用域的位置* 换句话说,作用域是一开始就定好的* */let x = 10;function fn() {console.log(x); //输出10}function show(f) {let x = 20;f();}show(fn);
面试题2
<script type="text/javascript">let fn = function () {console.log(fn)}fn()let obj = {fn2: function () {console.log(fn2) //报错,fn是变量,fn2是属性console.log(this.fn2) //可以输出函数}}obj.fn2()</script>
闭包
00 基本概念
* 理解:* 当嵌套的内部函数引用了外部函数的变量时就产生了闭包* 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性* 作用:* 延长局部变量的生命周期* 让函数外部能操作内部的局部变量* 写一个闭包程序
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f();
f();
* 闭包应用:* 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为* 循环遍历加监听* JS框架(jQuery)大量使用了闭包* 缺点:* 变量占用内存的时间可能会过长* 可能导致内存泄露* 解决:* 及时释放 : f = null; //让内部函数对象成为垃圾对象
01 理解闭包
1. 如何产生闭包?* 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包* 执行函数定义就会产生闭包(不用调用内部函数)2. 闭包到底是什么?* 使用chrome调试查看* 理解一: 闭包是嵌套的内部函数(绝大部分人)* 理解二: 包含被引用变量(函数)的对象(极少数人)* 注意: 闭包存在于嵌套的内部函数中3. 产生闭包的条件?* 函数嵌套* 内部函数引用了外部函数的数据(变量/函数)
02 常见的闭包
// 1. 将函数作为另一个函数的返回值function fn1() {let a = 2function fn2() {a++console.log(a)}return fn2}fn1()()//3fn1()()//3fn1()()//3let f = fn1()f()//3f()//4f()//5
03 闭包的作用
1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)问题:1. 函数执行完后, 函数内部声明的局部变量是否还存在? 一般是不存在, 存在于闭中的变量才可能存在2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它
04 闭包的生命周期
1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)2. 死亡: 在嵌套的内部函数成为垃圾对象时<script type="text/javascript">function fn1() {//此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)let a = 2function fn2 () {a++console.log(a)}return fn2}let f = fn1()f() // 3f() // 4f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)</script>
05 闭包的应用: 定义JS模块
* 具有特定功能的js文件* 将所有的数据和功能都封装在一个函数内部(私有的)* 只向外暴露一个包信n个方法的对象或函数* 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
自定义JS模块一
通过函数的return
function myModule() {//私有数据let msg = 'Hello JavaScript'//操作数据的函数function doSomething() {console.log('doSomething() ' + msg.toUpperCase())}function doOtherthing() {console.log('doOtherthing() ' + msg.toLowerCase())}//向外暴露对象(给外部使用的方法)return {doSomething: doSomething,doOtherthing: doOtherthing}}
<script type="text/javascript" src="myModule.js"></script><script type="text/javascript">let module = myModule()module.doSomething()module.doOtherthing()</script>
自定义JS模块二
匿名函数自调用,将对象添加到window中
(实际开发中肯定不允许这么做,怎么能随便改变window的属性呢?我猜的)
(function (window) {//私有数据let msg = 'Hello JavaSript'//操作数据的函数function doSomething() {console.log('doSomething() ' + msg.toUpperCase())}function doOtherthing() {console.log('doOtherthing() ' + msg.toLowerCase())}//向外暴露对象(给外部使用的方法)window.myModule2 = {doSomething: doSomething,doOtherthing: doOtherthing}})(window)/*函数添加window参数,是为了代码压缩*/
<script type="text/javascript" src="myModule2.js"></script><script type="text/javascript">myModule2.doSomething()myModule2.doOtherthing()</script>
06 闭包的缺点
1. 缺点* 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长* 容易造成内存泄露2. 解决* 能不用闭包就不用* 及时释放
<script type="text/javascript">function fn1() {let arr = new Array(100000)function fn2() {console.log(arr.length)}return fn2}let f = fn1()f()f = null //让内部函数成为垃圾对象-->回收闭包</script>
07 面试题1
<script type="text/javascript">//代码片段一let name = "The Window";let object = {name: "My Object",getNameFunc: function () {return function () {return this.name;};}};alert(object.getNameFunc()()); //? the window//object.getNameFunc()是object执行的,此时this是指向object的,并且它返回了一个函数//object.getNameFunc()()直接执行了返回的函数//这里没有闭包//代码片段二let name2 = "The Window";let object2 = {name2: "My Object",getNameFunc: function () {let that = this;return function () {return that.name2;};}};alert(object2.getNameFunc()()); //? my object//这里有闭包!</script>
07 面试题2
<script type="text/javascript">function fun(n, o) {console.log(o)return {fun: function (m) {return fun(m, n)}}}let a = fun(0)a.fun(1)a.fun(2)a.fun(3)//undefined,0,0,0let b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2let c = fun(0).fun(1)c.fun(2)c.fun(3)//undefined,0,1,1</script>
内存溢出与内存泄露
00 基本概念
1. 内存溢出* 一种程序运行出现的错误* 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误2. 内存泄露* 占用的内存没有及时释放* 内存泄露积累多了就容易导致内存溢出* 常见的内存泄露:* 意外的全局变量* 没有及时清理的计时器或回调函数* 闭包
/*1. 内存溢出*/let obj = {}for (let i = 0; i < 1000; i++) {obj[i] = new Array(1000000000);//这里obj[i] 是伪数组,obj里面会有属性名为0、1、2、3...999}console.log(obj)
/*2. 内存泄露*/// ①意外的全局变量function fn() {a = new Array(10000000) //这里没有写var、let来定义变量,所以这是向上找a,找不到,就在window中添加a属性并赋值了。console.log(a)}fn()// ②没有及时清理的计时器或回调函数let intervalId = setInterval(function () { //启动循环定时器后不清理console.log('----')}, 1000)// clearInterval(intervalId)// ③闭包
