- 三种基础数据结构
-
栈
乒乓球盒子(如下图):FIFO先进先出
数组Array提供了两个栈方法:push()和pop()
- 堆
堆数据结构通常是一种树状结构
var testHeap={
a:10,
b:20,
c:{
m:100,
n:10,
}
}
//当我们要访问a时,只用testHeap.a即可,m:testHeap.c.m
- 列队
queue也是FIFO
-
7种数据类型:
引用数据类型与堆内存空间
var a = 20;
var b = a;
b = 30;
//此时引用数据类型发生了一次复制行为,即b复制了一次a,最终b=20,a=20;
var m = { a: 10, b: 20 }
var n = m;
n.a = 20;
//此时m.a也为20,因为{a:10,b:20}作为一个数据存入堆中,
//而n与m引用的是同一个堆,于是堆内容发生改变,即m.a==n.a=20;
- 执行上下文
JavaScript包括一下三种环境:
- 全局环境:代码首先进入全局环境
- 函数环境:函数被调用时,进入函数中执行代码
- eval环境:不建议使用
上下文例子
- 变量对象
//demo1
var a=30;
//相当于以下:
var a= undefined;
a=30;
//end
//demo2
console.log(a)
var a=30;
//相当于
var a=undefined
console.log(a)
a=30
//end
注意:变量创建过程中,函数声明的优先级比变量声明的优先级更高,同名函数会覆盖函数与变量,同名的变量并不会覆盖函数。
var a=20;
function fn(){console.log('fn')}
function fn(){console.log('cover fn')}
function a(){console.log('cover a')}
console.log(a);
fn();
var fn='I want cover function named fn';
console.log(fn);
//20
//cover fn
//I want cover function named fn
//正确顺序为
function fn(){console.log('fn')}
function fn(){console.log('cover fn')}
function a(){console.log('cover a')}
var a=undefined;
var fn=undefined;
a=20;
console.log(a);
fn();
fn='I want cover function named fn'
console.log(fn);
//注意点:
//1、后面创建的fn函数覆盖了前面的fn函数
//2、后面的fn变量没有覆盖fn函数
- 例子
function test() {
console.log(foo);
console.log(bar);
var foo = 'Hello'
console.log(foo);
var bar = function() {
return 'world'
}
function foo() {
return 'hello';
}
}
test()
//正确顺序
function test() {
//函数声明
function foo() {
return 'hello';
}
console.log(foo);
var bar = undefined;
console.log(bar);
var foo = undefined;
foo = 'Hello';
bar = function() {
return 'world'
}
console.log(foo)
}
//demo2
function f1(){
var n=999;
nAdd=function(){
n+=1;
}
//nAdd这个时候是在预编译的过程直接弄成全局变量了,即var nAdd=undefined;
}
//调用方法f1()后,nAdd才被赋值
f1();
//一开始的时候,js只会知道有这么一个f1()函数,而不知道里面有什么东西,只有调用f1()的时候,才会对f1进行预编译,然后var n=undefined;n=999;nAdd=function(){...}
- 作用域与作用域链
- 全局作用域:window对象本身,一般变量的创建也拥有全局作用域
- 函数作用域:在函数中的作用域,函数执行完后即消失
- 块级作用域:在ES6的时候新增,
用{},let命令创建,外层作用域无法获取内层作用域,安全明了。即使有同名变量也互不干扰。
function fn1(){
let a=41;
if(1==1){
let a=3;
console.log(2,a);//2,3
}
console.log(1,a);//1,41
}
fn1();
//答案
//2,3
//1,41
//在{}中的块级作用域
//demo2
{
let fa='111'
let fn=function(){
console.log("我在函数里面");
}
console.log(fa,fn)//111 fn(){console.log("我在函数里面!")}
}
//demo3
//1.正确
if(4>2){
let fn=function(){"我在块级作用域里面"};
fn;
}
//错误
if(4>2)
let fn=function(){"错误的"};
//Uncaught SyntaxError: Lexical declaration cannot appear in a single-statement context
- 作用域链
var a=20;
function test(){
var b=a+10;
function innerTest(){
var c=10;
return c+b;
}
return innerTest();
}
test();
//其中 innerTest()的作用域链包含global,test,innerTest,三个作用域
-
闭包*
三要素:局部变量,函数,函数调用局部变量,
- 定义/解释:函数和函数内部访问自身外部的局部变量的时候,就会产生一个闭包。/在外部可以间接访问其他函数内部变量的函数,叫做闭包
//举个例子咯:
(function() {
var a = 20;
function bar() {
// return a;
console.log(a);
}
bar();
})();
//当a是一个局部变量的时候,我们通过bar()去访问获取这个局部变量的时候,就会产生一个闭包。
- 闭包的作用:有人说,闭包常常用来间接访问一个变量,又或者说,目的是为了保证这个变量的安全性,隐藏一个变量。
举例子:
写个程序:游戏还剩几条命的代码,
不用闭包的情况:可以直接用个全局变量:window.lives=30;
这样可以试想一下我们做外挂,我可以随意更改这个全局变量的值,来达到不死状态,所以这个时候我们应该隐藏起来这个生命值lives,不能让别人访问到。
解决方案:用局部变量的方法:暴露出一个加lives的方法和一个减lives的方法,不用直接去暴露lives这个变量,这里便使用闭包了。
var per=(function(){
var lives=100;
return {
Add(){
lives++;
},
del(){
lives--;
}
}
})();
- 理解闭包的过程:一开始我也不是一遍就懂了闭包的原理,什么时候会产生闭包,什么时候不能产生闭包,有时候判断不出来,但是随着我渐渐把作用域的知识点掌握了以后,你会发现,其实闭包的什么不重要,在作用域的知识支撑下,自然而然你就会知道,分析出来,闭包什么时候会产生,某个变量什么时候无法调用等等。
//demo1
var name = 'window';
var p = {
name: "peter",
getName: function() {
var self = this;
return function() {
return self.name;
//这里的self是p对象的上下文,所以返回的是peter,没有产生闭包,因为p.name是自身的一个变量。
}
}
}
var getName = p.getName();
var _name = getName();
console.log(_name);
//demo2
var name = 'window';
var p = {
name: "peter",
getName: function() {
return function() {
return this.name;
//这里的this是window的上下文,所以返回的是window,没有产生闭包,因为name是全局变量。
}
}
}
var getName = p.getName();
var _name = getName();
console.log(_name);
// demo3
var name = 'window';
var p = {
name: "peter",
getName: function() {
return function() {
return this.name; //这里的this是p对象的上下文,所以返回的是peter,没有产生闭包
}
}
}
var getName = p.getName();
var _name = getName.call(p); //用call方法让this指向p对象
console.log(_name);
- this*
- 当前函数的this是在函数被调用执行的时候才确定下来的。
- 全局的this:在全局对象中,this指向它本身。
- 函数中的this:在一个函数的执行上下文中,this由该函数的调用者提供,由调用函数的方式来决定其指向。
function fn(){
console.log(this);
}
fn();//fn为调用者
//调用者被一个对象所拥有,则调用该函数时,内部的this指向该对象,
//同理,如果是函数独立调用,则this指向undefined,默认undefined转为window。如下:
//demo
var a=20;
var obj={
a:40
}
function fn(){
console.log(this)
function foo(){
console.log(this.a);
}
foo();
}
fn.call(obj);//fn指向obj对象,this指向obj,foo独立调用,this指向undefined=>window
fn();//fn独立调用,this指向undefined=>window,foo独立调用,this指向undefined=>window
//demo1
var a=20;
var foo={
a:10,
getA:function(){
return this.a;
}
}
console.log(foo.getA());//10,getA()为调用者,被foo调用
var test=foo.getA;//test与foo.getA的引用指向同一个函数
console.log(test());//20 test独立调用
//demo2
function foo() {
console.log(this.a)
}
function active(fn) {
fn();
}
var a = 20;
var obj = {
a: 10,
getA: foo,
active: active
}
active(obj.getA) //active独立调用,window调用了obj.getA(),而obj.getA和foo的引用指向同一个函数,所以打印的是20
obj.active(obj.getA); //active被obj调用,到active方法中,foo独立调用,默认指向全局,即打印20
//demo3
var n = 'window'
var object = {
n: 'object',
getN: function() {
return function() {
return this.n;
}
}
}
console.log(object.getN()());
//一开始是object调用了getN,然后返回了一个函数,然后这个函数在独立调用,即变为window,
//object.getN()()==>function(){return this.n}()==>window.function()
- call/apply/bind显示指定this:
call/apply大同小异,参数传递不同
function fn(num1,num2){
return this.a+num1+num2;
}
var a=20;
var object={a:40}
fn.call(object,10,10)
fn.apply(object,[10,10])//call与apply的参数传递不同,答案都为60
bind函数不会立即执行,而是返回一个新的函数,函数与原函数不同,数据被绑定无法修改。
function fn(num1,num2){
return this.a+num1+num2
}
var a=20;
var object={a:40}
var _fn=fn.bind(object,1,2);
console.log(_fn===fn);//false
_fn();//43
_gn(1,4)//43,,数据被绑定,仍然计算40+1+2;
- 函数式编程:
- 函数声明、函数表达式、匿名函数、自执行函数、
10.面向对象:
对象的定义:无序属性的集合,包含基本值,对象或者函数。
创建:var obj=new Object();
var pbj={};
添加属性与方法:var person={}; person.name="Tom"; person.getName=function(){return this.name;}
原型:Person.prototype为Person的原型,在原型上添加方法:
Person.prototype.getName=function(){return this.name};
构造函数的原型:prototype
实例对象的原型: _ _ proto_ _
原型对象:constructor
构造函数的prototype与所有实例的_ _ proto _ _都指向原型对象。而原型对象的constructor则指向构造函数。
原型中的方法仅仅只会被创建一次。
- 具体某一个人的特定属性,通常放在构造函数中,所有人公共的方法与属性,放在原型对象中:
var Person=function(name,age){
this.name=name;
this.age=age;
}
Person.prototype.getName=function(){return this.name;}
var p1=new Person('jake',20);
var p2=new Person('tom',19);
p1.getName();//jake
p2.getName();//tom
- new关键字创建实例的过程:
1、先创建一个新的,空的实例对象;
2、将实例对象的原型(var res={};res. _ _ proto_ _ )指向构造函数的原型(func.prototype)
3、将构造函数内部的this,修改为指向实例;
4、最后返回该实例对象。
Person.prototype→PPrototype;
p1.proto→PPrototype;
p2.proto→PPrototype;
PPrototype.constructor→Person;
- 更简洁的原型写法:
function Person(){}
Person.prototype.getName=function(){}
Person.prototype.getAge=function(){}
Person.prototype.getsayHello=function(){}
//下面是简洁写法
Person.prototype={
constructor:Person,
getName:function(){},
getAge:function(){},
getsayHello:function(){},
}
Person.prototype={}时,Person的原型指向了一个新的对象{},如果不做特殊处理,则会导致原型丢失,所以把constructor属性指向构造函数Person,这样子就重新建立了正确的对应关系。
ES6与模块化
- let/const
var a=20;
//等同于
var a=undefined;
a=20;
//使用let/const时,提升的操作仍然存在,但是不会复制undefined,
//也就是说,声明提前了,但是该变量并没有任何引用,所以会报错。
console.log(a);//Reference Error:a is not defined
let a=20;
- 暂时性死区:在大括号内,作用域为let的作用域,console.log(a)的a依然是下面的let的a。
var a=20;
if(true){
console.log(a);//Reference Error:a is not defined
let a=30;
}
- let与const的区别:let 可以被改变,const不能改变。
声明一个引用性的数据时,也可以使用const
const b={};
b.max=20;
b.min=0;
console.log(b);//{max:20,min:0}
在上图中,temp用const定义,而temp只定义一次,不可改变,所以报错。
- 箭头函数
- 箭头函数只能替换函数表达式,即用var\let\const声明,直接使用function函数声明的函数不能替换。
- 箭头函数的this指的就是上下文中的this,它不会被其他方式所改变。
- 箭头函数中没有arguments对象。
//ES5
var fn=function(a,b){
return a+b;
}
var foo=function(){
var a=20;
var b=3;
return a+b;
}
//ES6
const fn=(a,b)=>a+b;
const foo=()=>{
const a=20;
const b=30;
return a+b;
}
- 模板字符串: 用反引号“ ` ”将整个字符串包裹起来,变量用“${}”进行包裹
- ${}中可以放变量,表达式,甚至函数
//ES5
var a=20;
var b=30;
var s=a+"+"+b+"="+(a+b);
//s:20+30=50
//ES6
const a=20;
const b=30;
const s=`${a}+${b}=${a+b}`
//s:20+30=50
let fn=()=>{
const r='you are the bset';
return r;
}
let str=`he said :${fn()}`
- 解析结构:
var Tom={
name:'Tom',
age: 20,
gender :1 ,
job:'student'
}
const{name.age.gender.job}=Tom;
//这样就可以获取这些信息,相当于
const{name:name,age:age,gender:gender, job:job}=Tom;
//定义默认值:Tom中能找到name,那么name的值取tom的,找不到则用默认值jake。
const {name='jake' stature='170'}=Tom;
//重新命名:将gender重新命名为t,变量则变成t,Tom中仍然为gender
const{gender:t,job}=Tom;
//获取嵌套数据的值:
const peoples={ count:100,tom:{ name:'tom',age:20} };
const {tom:{name}}=peoples //获取tom中的name
const arr=[1,2,3]
const [a,b,c]=arr;
//等价于
const a=arr[0];
const b=arr[1];
const c=arr[2];
- 展开运算符
const arr1=[1,2,3]
const arr2=[...arr1,4,5,6]
//arr2为:[1,2,3,4,5,6]
//对象同理:
object1={a:1,b:2,c:3}
object2={...object1,d:4,e:5,f:6}
//object2={a:1,b:2,c:3,d:4,e:5,f:6}
- 在解析结构中也可使用
- 在函数参数放最后面表示不定数量的参数
- promise
- new Promise创建一个Promise实例对象
- Promise的第一个参数为回调函数,可以称之为executor
- 回调函数返回的状态有三种:pending,resolved,rejected, 请求默认结果为pending
- 在回调函数中,可以使用resolve()和reject()两个方法将状态修改为resolved和rejected
- then接收resolve传递出来的数据;catch接收reject的数据
function fn(num){
return new Promise(function(resolve,reject){
if(typeof num== 'number'){
resolve();
}else{
reject();
}
}).then(function(){
console.log('参数是一个number');
}).catch(function(){
console.log('参数不是一个number')
})
}
fn('12');
//注意观察该语句的执行顺序
console.log('next code')
//next code
//参数不是一个number
- then可以接收resolve的参数还有reject的参数
fn('abc').then( function(res){console.log(res)} , function(err){console.log(err)} )
- then 可以嵌套调用
function fn(num){
return new Promise(function(resolve,reject){
if(typeof num== 'number'){
resolve(num);
}else{
var err='false';
reject(err);
}
})
}
fn('12').then(function(res){
console.log(res);//12
return res+1;
}).then(function(res){
console.log(res);//13
return res+1;
}).then(function(res){
console.log(res);//14
}).then(function(res){
console.log(res);//undefined
})
- promise.all 发起多个请求,当所有请求均为resolve时,promise.all返回resolve,如果有一个是错的,返回reject
- promise.race 与primise.all相似,也是发起多个请求,不一样的点是,如果有一个请求变为rejected或者是resolved,这么请求可以调用.then()进行操作
function renderRace(){ return Promise.race( [getJSON(url),getJSON(url1)] ) }
renderRace().then(function(value){ console.log(value) } )
//这里的then为rejected和resolved所调用
- async/await:一部问题不仅可以使用Promise解决,还可以用async/await(本质也是promise,是Promise的一个语法糖)
//写法1
async function fn(){
return 30;
}
//写法2
const fn= async()=>{
return 30;
}
console.log(fn())
所以可以有
fn().then(res=>{
console.log(res);
})
- await:等待await部分执行完毕再继续下一步。
function fn(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(30);
},1000)
})
}
const foo=async()=>{
const t=await fn();
console.log(t);
console.log("next,code");
}
foo()
//答案:30,next code
- 捕获异常:async/await捕获异常用trycatch,如果有多个异常,返回第一个异常。
const foo=async()=>{
try {
await fn();
} catch (err) {
console.log(e)
}
}
foo()
- 事件循环机制:列队原理
- 任务列队分为宏仁务(macro-task)和微任务(micro-task)两种,在浏览器中,包括:
- macro-task:script(整体代码),setTimeout/setInterval,I/O,UI rendering等。
- micro-task:Promise
//demo
setTimeout(function(){
console.log("timeout1")
},1000)
new Promise(function(resolve){
console.log('promise1');
for(var i=0;i<1000;i++){
i==99&&resolve();
}
console.log('promise2');
}).then(function(){
console.log('then1');
})
console.log('global1');
一:script任务开始,script入列队,global全局上下文入栈;
二:script中遇到了setTimeout,setTimeout为Macio-task,进入列队;
三:script执行遇到Promise,promise的第一个参数,是在new创建实例的时候进行,因此不会进入到任何其他的列队,而是在当前任务直接执行,后续的.then则回备分发到micro-task的列队中去。
构造函数执行时,里面的参数进入函数调用栈执行。for循环不会进入任何列队(意思是:for里面的resolve会声明,但不会执行。完毕后出栈,然后promise2再输出。),因此代码执行时promise1和promise2会依次输出。
四:输出global。第一轮,全局任务结束,进入第一轮的micro-task部分。
五:micro-task中有一个.then,输出then1。第一轮全部结束。
六:第二轮仍然从macro-task出发,发现setTimeout中有个timeout1没有输出,进入调用栈输出。
答案:promise1, promise2, global1, then1, timeout1