一、闭包
1、作用域的生命周期
- 全局作用域:进入页面时创建,关闭页面时销毁
- 局部作用域:函数执行时创建,执行完毕销毁
2、闭包
- 定义:闭包是一个函数以及其捆绑的周边环境状态的引用的组合
- 闭包与 函数、作用域、垃圾回收机制 息息相关
- 分类
- 无效闭包:函数嵌套;内部函数引用了外部函数的变量
function outer(){ var count = 0 function inner(){ count++ console.log(count) } inner() } outer() //1 outer() //1 outer() //1 - 有效闭包:
- 函数嵌套
- 内部函数引用了外部函数的变量
- 在函数外部保持对函数内部的引用和调用
function outer(){ function outer(){ var count = 0 var count = 0 function inner(){ => return function(){ count++ count++ console.log(count) console.log(count) } } return inner } } var fn = outer() //保持对函数的引用和调用 fn() //1 fn() //2 fn() //3 - 优点
- 在外部操作局部变量,延长了生命周期
- 在外部知道函数内部的执行状态
- 局部变量在函数执行后不会销毁,闭包是函数外部和内部的沟通桥梁
- 缺点
- 占用内存空间 ->尽量少使用闭包
- 很容易造成内存泄漏 ->及时释放内存空间:
fn = null
- 无效闭包:函数嵌套;内部函数引用了外部函数的变量
3、文档碎片
html文档中的Dom操作性能消耗很大,如果频繁操作DOM会带来页面的性能问题
- 文档碎片(DocumentFragment)
- 是一种轻量级的节点对象,是一种虚拟节点对象
- 通常作为一个容器使用(空容器),不存在DOM树中
let fragment = document.createDocumentFragment()
二、作用域
1、变量提升
- 声明提升机制: 在JS中函数声明或变量声明时,'声明'会提升到当前作用域顶部
var a = 1; function test(){
function test(){ console.log(a); // undefined
console.log( a ); => var a = 2;
var a = 2; console.log(a); // 2
console.log( a ); }
} var a;
test(); a = 1;
test()
变量和函数的优先级:函数>变量
形参和实参的优先级
var bb = 1; function aa(){...}
function aa(bb) { var bb = undefined;
bb = 2; bb = 1
alert(bb); //2 => aa(bb); //1
}; function aa(1){
aa(bb); bb = 1 -> 2
alert(bb); alert(bb); //2
}
alert(bb); //1
2、JS解析器 (为什么存在变量提升?
-
JS解析器:浏览器中有一套专门解析执行JS代码的程序就叫解析器(JS引擎)
-
工作步骤:
- 1.预解析代码
- 主要工作扫描一些关键字 var function等,和解析参数(形成和实参)保存在内存中
- 变量 形参 的默认值为 undefined
- 函数预解析时值为函数本身
- 当变量和函数重名时,函数优先
- 当形参和局部变量重名时,形参优先
- 2.逐行执行代码
- 按照书写顺序从上到下依次执行
- 在执行过程中,内存中的变量值随时可能被修改
var a = 1; function test(){ console.log( a ); var a = 2; console.log( a ); } test();//1.解析全局作用域 //3.解析局部作用域 fn() { { a:undefined; => 1 a:undefined; => 2 test:function (){ arguments:[length:0] console.log( a ); } var a = 2; console.log( a ); } } //2.逐行执行代码 //4.逐行执行代码 a = 1; console.log( a ); //undefined test() a = 2; console.log( a ); //2 - 1.预解析代码
3、作用域链 (解释器工作原理
-
定义
- 作用域链简单讲,是多个作用域嵌套形成的由内到外的结构,用于查找变量(变量 函数 参数等)
- 作用域链本质上是一个指向变量对象的指针列表
-
执行上下文(执行环境)
- 每一个执行上下文都包含 变量对象/活动对象 、arguments、this 、作用域链等信息
- 在每一个段代码执行之前,都会先创建好这段代码的执行上下文
- 进入页面时,浏览器解析到
<script>标签时,JS引擎开始工作,先创建全局执行上下文 - 当调用一个函数时,先创建该函数的执行上下文
-
模拟执行环境
- 执行上下文(执行环境) Excution Context -> EC
- 变量对象 Variable Object -> VO
- 活动对象 Activation Object -> AO
- 作用域链 Scope Chain -> SC
console.log( this );// window var x = 1; function test(a,b){ var y = 2; console.log( a + b ); console.log( x + y ); console.log( this ); console.log( abc ); } test(3,4);//1.创建执行环境 global_EC={ //创建全局执行环境 VO:{ //变量对象 == window对象 x:undefined; => 1 test:function test(){...}; }, this:windows, SC:[global_EC.VO] //作用域链 } test_EC={ //函数执行环境 AO:{ //活动对象 a:undefined; => 3 b:undefined; => 4 y:undefined; => 2 }, this:windows, arguments:[3,4;length:2;callee:test], SC:[ test_EC.AO , global_EC.VO ] } //2.逐行执行代码 console.log( this ); //window (global_EC.VO x = 1; // global_EC.x undefined => 1 test(3,4) y = 2; //作用域链查找,test_EC.AO -> global_EC.VO console.log( a + b ); // 3+4 console.log( x + y ); // 1+2 console.log( this ); // window console.log( abc ); //报错
4、词法作用域(静态作用域)
- 静态作用域:主要关注的是函数在哪定义,而不是关注它在哪调用
- 动态作用域:主要关注的是函数在哪调用,而不是它在哪定义
var a = 10; function fn1(){ console.log( a ); } function fn2(){ var a = 20; fn1(); } fn2();//1.创建执行环境 golbal_EC={ VO:{ a:undefined; => 10 fn1:function fn1(){...}; fn2:function fn2(){...}; }, this:window, SC:[golbal_EC.VO] } fn1_EC={ fn2_EC={ AO:{ }, AO:{ this:window, a:undefined; => 20 SC:[fn1_EC.VO , golbal_EC.VO] fn1:fuction fn1(){...} } }, this:window, SC:[fn2_EC.VO , golbal_EC.VO] } //2.逐行执行代码 a = 10; undefined -> 10 fn2() a = 20; fn1() console.log( a ); // 10 (fn1_EC.VO -> golbal_EC.VO
5、IIFE函数(立即执行函数)
(function (){// 匿名函数
console.log( '立即执行函数' );
})();
- 原理:
- js中能立即被执行的只有表达式,普通函数被定义不会立即执行,而是等待被调用
- 运算符避开了JS预解析,将函数声明转化成了表达式,使其可以立即执行
- 多种写法
+function (){...}(); -function (){...}(); !function (){...}(); let result =( function (a,b){return a+b }(1,3) ) console.log(result) - 作用
- 提供一个封闭的空间(局部作用域)
- 不污染外部命名空间(局部作用域)
立即执行函数的变量提升
var a = b = 10; var a = b : undefined
(function () { a = b = 10
var a = b = 20; => (function () {
})(); var a : undefined
console.log(b); a = b = 20 //var a=20(局部),b=20(全局)
})(); //IIFE避开js预解析,不提升(相当于fn())
console.log(a); //10
console.log(b); //20
三、原型链
1、构造函数:用于创建特定类型的对象
- 对象的
constructor属性,返回创建该对象的构造函数console.log( num.constructor );// Number var num = 123; => String var str = 'abc'; => Number var boo = true; => Boolean var arr = [1,2,3]; => Array var obj = {a:123}; => Object var fun = function (){}; => Function
this指向:谁调用指向谁
- 1.在全局作用域使用时,this指向window对象
- 2.函数找不到调用对象时,this指向window对象
//1.非严格模式 let fn = function(){}; fn() //this -> window //2.严格模式 let fn = function(){}; fn() //this -> undefined - 3.this所在的函数,谁调用该函数this就指向谁
let obj={ say:function(){console.log(this)} } obj.say() //this -> obj - 4.在构造函数中,this指向创建出来的实例对象
function Person(name,age){ this.name=name; this.age=age //this -> 实例对象 } - 5.事件处理函数中,this指向添加事件的dom对象
btn.onclick = function (){ console.log( this );// btn } btn.show() //this -> btn
改变this指向 : bind(),call(),apply()
let obj1 = {
name: 'obj1',
fn: function (a, b) {
console.log(this.name);
return a+b
}
};
let obj2 = {
name: 'obj2',
};
-
bind和call/apply的区别:bind不会执行前面的函数( ),返回一个新的函数- 若参数为基本数据类型,则自动包装成对象
- 定时器必须用bind( )
let res = obj1.fn.bind(obj2, 1, 2)(); //obj2 3 console.log(res === obj1.fn); //false //若参数为基本数据类型,则自动包装成对象 function fn (){console.log(this)} let num = 2 fn.bind(num)() // Number {2} //定时器必须用bind() 不需要立即执行 setTimeout (function (){...}.bind(obj,1,2),1000)call/apply会执行前面的函数,返回函数的结果let res = obj1.fn.call(obj2, 3, 4); //obj2 7 let res = obj1.fn.apply(obj2,[5,6]); //obj2 11
-
call和apply的区别:call传参: 逗号隔开的参数列表 a,b,capply传参: 数组 [a,b,c]
2、prototype原型(显式原型)
- 每个函数(构造函数)都有一个prototype属性,属性值为一个对象,即原型对象
- 当创建一个函数时,JS引擎自动为函数添加prototype属性
- 非函数对象,没有prototype属性
- 作用: 所有添加到原型对象上的属性方法,都将被函数的实例对象共享(继承)
let fn = function(){}; console.log(fn.prototype); //{constructor: fn} 原型对象 let num=123; //非函数对象 console.log( num.prototype ); // undefined - ECMA内置构造函数: Number String Array Boolean Object Function...
console.log( Number.prototype );// {constructor: Number} console.log( String.prototype );// {constructor: String} console.log( Array.prototype );// {constructor: Array } console.log( Object.prototype );// {constructor: Object}
3、__proto__原型(隐式原型)
- 每个对象都有一个隐藏属性__proto__,指向创建它的构造函数的
prototype属性 __proto__作用:用于维护原型链当我们访问对象的属性时,JS引擎优先在对象自身查找该属性,如果自身没有该属性, 通过对象的__proto__属性向上层原型对象查找,一直到nulllet num=123; console.log( num.__proto__ === Number.prototype );// true function Human(name){ this.name = name; }; var zhangsan = new Human('张三'); console.log( zhangsan.__proto__ === Human.prototype);// true
4、原型链
let num = 123
String.prototype.haha = '哈哈'
Object.prototype.hehe = '呵呵'
console.log(num.haha) // undefined
console.log(num.hehe) //'呵呵'
//1.将基本数据类型包装成对应的object类型
let _num = new Number(123);
//2.通过对象访问属性
_num -> _num._proto_ -> Number.prototype -> Number.prototype._proto_->
Object.prototype -> Object.prototype._proto_ -> null
作者制定的四条路线
-
Function.
_proto_= Function.prototype -
Object.
_proto_= Function.prototype原因:Object、Function作为函数,应该能使用bind()...等方法 -
Object.prototype.
_proto_=== null原因:避免对象原型链无限循环 -
Function.prototype.
_proto_= Object.prototype原因 :Function.prototype的值是一个函数,在JS中函数也是对象 对象都应该能访问Object.prototype原型对象上的属性方法 console.log( Function.prototype );// ƒ () { [native code] } 函数
原型链案例
function fn(name, age) {
this.name = name;
this.age = age;
}
fn.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name);
};
let f1 = new fn('Bob', 25); //实例对象
// 原型链
/* f1 -> f1._proto_ -> fn.prototype -> fn.prototype._proto_ ->
Object.prototype -> Object.prototype._proto_ -> null
Object._proto_ === Function.prototype
Object.__proto__.constructor === Function
Object.prototype.constructor === Object
fn.__proto__ === Function.prototype
*/
// f1找不到Function.prototype
Function.prototype.a = function(){
console.log('a...');
}
Object.prototype.b = function(){
console.log('b...');
}
f1.b() //b...
f1.a() //报错