JavaScript核心技术开发解密

  1. 三种基础数据结构

  • 乒乓球盒子(如下图):FIFO先进先出
    数组Array提供了两个栈方法:push()和pop()


    ppq.jpg

  • 堆数据结构通常是一种树状结构
var testHeap={
  a:10,
  b:20,
  c:{
      m:100,
      n:10,
    }
}
//当我们要访问a时,只用testHeap.a即可,m:testHeap.c.m
  • 列队
    queue也是FIFO
  1. 7种数据类型:


    js数据类型.png
  2. 引用数据类型与堆内存空间


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;
  1. 执行上下文
    JavaScript包括一下三种环境:
  • 全局环境:代码首先进入全局环境
  • 函数环境:函数被调用时,进入函数中执行代码
  • eval环境:不建议使用
    上下文例子
  1. 变量对象
//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(){...}
  1. 作用域与作用域链
  • 全局作用域: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,三个作用域
  1. 闭包*
    三要素:局部变量,函数,函数调用局部变量,
  • 定义/解释:函数和函数内部访问自身外部的局部变量的时候,就会产生一个闭包。/在外部可以间接访问其他函数内部变量的函数,叫做闭包
//举个例子咯:
(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);
  1. 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;
  1. 函数式编程:
  • 函数声明、函数表达式、匿名函数、自执行函数、

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与模块化

  1. 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只定义一次,不可改变,所以报错。

  1. 箭头函数
  • 箭头函数只能替换函数表达式,即用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;
}
  1. 模板字符串: 用反引号“ ` ”将整个字符串包裹起来,变量用“${}”进行包裹
  • ${}中可以放变量,表达式,甚至函数
//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()}`
  1. 解析结构:
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];
  1. 展开运算符
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}
  • 在解析结构中也可使用
  • 在函数参数放最后面表示不定数量的参数
  1. 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所调用
  1. 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()
  1. 事件循环机制:列队原理
  • 任务列队分为宏仁务(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351