第一部分 作用域和闭包
JavaScript 中的函数运行在它们被定义的作用域里 , 而不是它们被执行的作用域里。一、作用域是什么
1.RHS查询:简单地查找某个变量的值
2.LHS查询:试图找到变量容器本身,从而进行赋值
3.作用域嵌套:向上寻找
二、词法作用域
1.意味着作用域是由书写代码时的函数声明的位置来决定的
2.作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,这叫遮蔽效应
三、函数作用域和块作用域
1.函数作用域:属于这个函数的全部变量都可以做整个函数的范围内使用及复用,使用最小特权原则隐藏内部实现,外部作用域无法访问包装函数内部的任何内容。
2.立即执行函数表达式【IIFE】 (function(){...}()) 或者 (function(){...})();
进阶方式:把它们当作函数调用并传递参数进去
function IIFE(global){
...})(window);
3.块作用域:
- let关键字可以将变量绑定到所在的任意作用域中(通常是{...}内部)
- 垃圾收集:块的存在可以让引擎清楚地知道没有必要保存某些不需要的代码段
- let循环:
四、提升:
1.变量提升
2.函数声明会被提升,但函数表达式却不会被提升
3.函数优先:函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量
五、作用域闭包:
闭包指有权访问另一个函数作用域的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另外一个函数,并返回。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
1.闭包:闭包就是能够读取其他函数内部变量的函数,闭包使得函数可以继续访问定义时的词法作用域。
function foo{
var a = 2;
function bar(){
console.log(a);
}
bar();
}
foo();
function foo(){
var a = 2;
function baz(){
console.log(a);//2
}
bar(baz);
}
function bar(fn){
fn();
}
2.无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
3.只要使用了回调函数,实际上就是在使用闭包
4.循环和闭包:
- 延迟函数的回调会在循环结束时才执行,根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。
for(var i = 1;i<= 5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}
- 需要更多的闭包作用域,IIFE会通过声明并立即执行一个函数来创建作用域
for(var i = 1;i<=5;i++){
(function(j){
setTimeout(function timer(){
console.log(j);
},j*1000);
})(i);
}
- 指出变量在循环过程中不止被声明一次,每次迭代都会声明,随后的每个爹地啊都会使用上一个迭代结束时的值来初始化这个变量。
5.模块:https://www.cnblogs.com/syfwhu/p/4883532.html
Module模式最初定义在传统的软件工程中,为类提供私有和公有封装的方法。在JavaScript中,并不能可以直接声明类,但我们可以使用闭包来封装私有的属性和方法,进而模拟类的概念,在JavaScript中实现Module模式。通过这种方式,我们就使得一个单独的对象拥有公有/私有方法和变量,从而屏蔽来自全局作用域的特殊部分,也就大大降低了变量声明之间和函数声明之间冲突的可能性。
通过使用闭包我们封装了私有变量和方法,而只暴露了一个接口供其他部分调用。
- 模块模式:
function cool(){
var a = 'cool';
var b = [1,2,3];
function doA(){
console.log(a);
}
function doB(){
console.log(b);
}
return{
doA:doA,
doB:doB
};
}
var foo = cool();
foo.doA();//cool
foo.doB();//1!2!3
- 单例模式:只需要一个实例
var foo=(function cool(){
var a = 'cool';
var b = [1,2,3];
function doA(){
console.log(a);
}
function doB(){
console.log(b);
}
return{
doA:doA,
doB:doB
};
})();
foo.doA();//cool
FOO.doB();//1!2!3
- ES6的模块支持
1:每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说 就是一个对象;
2:每一个模块内声明的变量都是局部变量, 不会污染全局作用域;
3:模块内部的变量或者函数可以通过export导出;
4:一个模块可以导入别的模块
import { square, diag } from './lib';
export{ bar , foo, fn0, fn1}
import * as obj from "./lib";
附录A 动态作用域
1.词法作用域:它的定义过程发送在代码的书写阶段
2.动态作用域:让作用域作为一个在运行时就被动态确定,作用域链基于调用栈的而不是代码中的作用域嵌套。js中不存在,this机制很像动态作用域。
附录B 块作用域的替代方案
{
let a = 2;
console.log(a);//2
}
console.log(a);//ERROR
try{throw 2;}catch(a){
console.log(a);//2
}
console.log(a);//error
附录C this词法
箭头函数解决的问题
var obj = {
id:"awesome",
cool:function cooFn(){
console.log(this.id);
}};
var id = "no";
obj.cool();//awesome
setTimeout(obj.cool,100);//no
方法一:cool()对视同this之间的绑定,用var self = this;解决
var obj = {
count:0;
cool:function coolFn(){
var self = this;
if(self.count<1){
setTimeout(function timer(){
self.count++;
console.log("a");
},100);
}
}
};
obj.cool();//a
方法二:箭头函数继承了cool()函数的this绑定
var obj = {
count:0,
cool:function coolFn(){
if(this.count<1){
setTimeout(()=>{
this.count++;
console.log("sophia");
},100);
}
}
};
obj.cool();//sophia
方法三:使用bind绑定
var obj = {
count:0,
cool:function coolFn(){
if(this.count<1){
setTimeout(function timer{
this.count++;
console.log("sophia");
}.bind(this),100);
}
}
};
obj.cool();//sophia
第二部分 this和对象原型
第一章 关于this
1.两种误解:this指向函数自身、this指向函数作用域
2.this是在运行时进行绑定的,并不是在编写时绑定的,它的上下文取决于函数调用时的各种条件。
第二章 this全面解析
1.this完全取决于函数的调用位置,调用位置就是函数在代码中被调用的位置(而不是声明的位置)
2.绑定规则:
- 默认绑定:非严格模式下this指向window,严格模式下指向undefined。eg:foo()
- 隐式绑定:在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数从而把this隐式绑定到这个对象上。会把函数调用中的this绑定到这个上下文对象,对象属性引用链中只有上一层或者最后一层在调用位置中起作用。eg:obj.goo();
- 隐式丢失:别名、传入回调函数的情况下,默认绑定绑到全局上。回调函数丢失this绑定非常常见。
- 显示绑定:call(),apply()
- 硬绑定:解决丢失绑定问题,典型的应用场景就是创建一个包裹函数,负责接收参数并返回值
var bar = function(){ return foo.apply(obj,arguments); };
bind()会返回一个硬编码的新函数,它会把你指定的参数设置为this的上下文并调用原始函数var bar = foo.bind(obj);
- new绑定:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
1.创建一个全新的对象 2.这个新对象会被执行[[Prototype]]连接 3.这个新对象会绑定到函数调用的this 4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
3.优先级:new绑定>显示绑定>隐式绑定>默认绑定
4.绑定例外:call(null/undefined)、apply(null,[2,3])、bind(null,2)、var o = Object.create(null)//创建非军事区
5.ES6中的箭头函数不会使用四条绑定规则,而是根据当前的词法作用域来决定this。箭头函数继承外层函数调用的this绑定,这其实和ES6之前代码中的self = this机制一样
第三章 对象
1.定义:声明形式和构造形式
2.六大基本类型:string、number、boolean、null、undefined、object
3.内置对象:String、Number、Boolean、Object、Function、Array、Date、RegExp、Error
4.复制对象:
- 浅复制:Object.assign(目标对象,源对象)
- 深复制:易导致死循环
5.属性描述符
- Object.getOwnPropertyDescriptor(myObject,"a");
- Object.defineProperty(myObject,"a",{});
- 可写、可枚举、可配置
6.不变性:
- 对象常量:writable:false;configurable:false
- 禁止扩展:Object。preventExtensions()禁止添加新属性并保留已有的值
- 密封:Object.seal(...)不能添加新属性,也不能重新配置或者删除任何现有属性
- 冻结:Object。freeze(...)最高级别的不可变性
7.[[Get]]操作:首先在对象中查找是否有名称相同的属性,如果找到就返回这个属性的值。如果没有就遍历可能存在的原型链。
8.[[Put]]操作:
9.Getter和Setter
var myObject = {
get a(){
return this._a_;
}
set a(val){
this._a_ = val;
}
};
10.遍历:
ES6增加一种用来遍历数组的for...of语法
for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。
第四章 混合对象“类”
1.面向类的设计模式:实例化、继承、多态(子类重写父类的方法)
【java三大特征:封装、继承、多态】
2.js实际上没有类,其他语言中的类和js中的类并不一样
3.混入(模拟类的复制行为)
在继承或实例化时,JavaScript的对象机制并不会自动执行复制行为。简单来说,JavaScript中只有对象,并不存在可实例化的类。一个对象并不会被复制到其他对象,它们会被关联起来。
- 显示混入:
function mixin(sourceObj,targetObj){
for(var key in sourceObj){
if(!(key in targetObj){
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
缺点:但是JavaScript中的函数无法真正地复制,所以你只能复制对共享函数对象的引用
- 寄生继承:
Vehicle.prototype.ignitoin = function(){...}
var car = new Vehicle();
- 隐式混入:
Something.cool.call(this)
第五章 原型
1.[[Prototype]]:对象的内置属性,对于其他对象的引用
var one = {
a:2
};
var two = Object.create(one);
two.a;//2
所有普通的[[Prototype]]链最终都会指向内置的Object.prototype。
.toString() .valueOf() .hasOwnProperty() .isPropertyOf()