javascript学习笔记


JavaScript Lambda表达式

本质上就是一个匿名函数,形式为:

    (pram1, param2,) => {
        //some operations

        return xxx;
    }

lambda表达式还能够依据不同参数&返回值的需求进行简化:

    //1、无参
        圆括号不能省略
        () => {
            //some operations
        }
    //2、独参省略圆括号
        当只有一个参数时可以省略圆括号
        param => {
            statements;
        }
    //3、省略花括号
        1、当整个方法体仅有一行、无返回值时,可以省略花括号和分号(必须省略分号)
        (parameters) => statement
        
        2、当整个方法体仅有一行,且该行为return xxx,可以省略花括号、return以及句末的分号(必须省略分号)
        (parameters)=> expression

    //注意:javascript的函数支持可变参数  
        function abc(){
            for(var i=0; i<arguments.length; i++){
                window.alert(arguments[i]);
            }
        }
        abc(2,5,6);

注意: lambda表达式作为参数时,其参数列表中的参数只是形参,并不能将上文中的具体变量名代入,这样方法体在执行时实际传入的参数值与声明lambda时与形参同名的上文变量无关,例如:


    function doAdd(a,b){
        setTimeout(() =>  console.log(a+b)
        ,1000)
    }

    doAdd(100,200)      //输出300 符合预期

    function doAdd(a,b){
        setTimeout((a,b) =>  console.log(a+b)
        ,1000)
    }

    doAdd(100,200)      //输出NaN  setTimeout实际执行的方法中a,b为undefined

我的理解: lambda表达式的本质就是一个匿名函数,它是被以这个lambda作为参数的函数caller调用的,lambda表示的匿名函数在被caller调用时,参数才会注入进去。


面向对象和函数式编程 oop&fp

编程的本质

  • 操作数据(读取和改变)

数据存放的差异

  • OOP:数据存放在对象的成员变量、全局变量中。
  • FP:数据存放在闭包(各级作用域)里面。

数据访问的差异

  • OOP:访问数据(除全局变量以外),需要获得对象的引用,再通过该对象访问公共/私有成员变量。
  • FP:直接通过函数入参或者作用域链查找并访问数据。

OOP所谓的对象,在本质上就是FP中的作用域,所以:
面向对象编程,实际上就是面向作用域编程、
函数式编程,则是面向功能编程。

OOP、FP各自的优势

OOP对数据作用域有着更严格的约束,高度的封装性保证了数据的安全性,更能适应迪米特原则和单一职责原则。 相对于FP而言,OOP对开发人员的要求更低,可以专注于整体需求中的一小部分逻辑进行编码。
FP有着作用域链这样一个巨大的优势,函数可以访问的变量范围要远远大于OOP,灵活性大幅度上升,可以将整个业务的逻辑集中在一起,更加直接、高效地完成需求。但这样也对开发人员对代码整体有着更深刻、细致的掌握。

javascript的优势

Javascript超越了函数式以及OO
在JS中约束进一步被打破,JS中可以修改函数的“作用域”,类的成员方法可以以另一个对象为作用域。JS还可以更换父类,这在其他OO语言是难以想象和不可理喻的。

所以Javascript可以用最简短代码来代替OO许多代码,究其本质,就是OO需要不断的传递、持有不同的作用域——对象。而Javascript只需要处在作用域链内,即可轻易访问到所需要的数据。


JavaScript的面向对象

  • ES6之前没有提供class这个关键字,是通过prototype来模仿面向对象中的类和类继承。
    function Animal(eat,sound){
        this.eat = eat;
        this.sound = sound;
    }
    Animal.prototype.makeSound = function(){
        console.log("this animal sounds like: "+this.sound);
    }
    Animal.prototype.doEat = function(){
        console.log("this animal eats :"+this.eat);
    }

    let anAnimal = new Animal("小鱼","喵喵喵");
    anAnimal.doEat();                            //小鱼
    anAnimal.makeSound();                        //喵喵喵

    console.log(anAnimal instanceof Animal);     //true
  • ES6引入了class这个关键字,有了一套更加正式的类与继承系统。
    ES6中class并不是一个全新的概念,它的底层原理依然是function和prototype
    class Vehicle
        constructor(color) {
            this.color = color;
        }

        showColor(){
            console.log(this.color);
        }

        modifyColor(newColor){
            this.color=newColor;
        }
    }

    let benz = new Vehicle("red",200,"五百万");
    benz.showColor();                   //red
    benz.modifyColor("green");          
    benz.showColor();                   //green


    class Porsche extends Vehicle{
        constructor(color){
            super(color);
        }
        runFast(){
            console.log("I am a porsche and I can run really fast!");
        }
        showColor(){
            console.log("I am porsche and my color is rare: 五光十色的黑!");
        }
    }
    let 破车 = new Porsche("yellow");
    破车.showColor();             //I am porsche and my color is rare: 五光十色的黑!  子类的showColor覆盖了父类中的同名方法
    破车.runFast();               //I am a porsche and I can run really fast!       子类独有的方法
    console.log(破车 instanceof Porsche);     //子类对象是该类的实例
    console.log(破车 instanceof Vehicle);     //子类对象也是父类的实例

这里有必要提一下this这个东西

  • 在javascript中,this不是固定不变的,会随着执行环境的改变而改变。
  • 在方法中,this表示该方法所属的对象,在上面的class实例中,this即可以访问到该对象的属性字段。
  • 在函数中
    • 非严格模式:this默认绑定到全局对象,在浏览器中对应的即是[object Window];在node中执行一个函数时,this是一个空对象{}
    • 严格模式(即在js文件最开始有"use strict"注解),this是undefined
  • 单独使用this时,无论是否严格模式,默认都会绑定到全局对象,在浏览器中对应的即是[object Window];

注意:以前写java时常常把函数方法当作一个概念,而这里前者指的是不属于某一个对象的function,而后者指的是某一个对象的成员方法。


👆2021.3.3晚更新


nodejs中的exports和module.exports

node.js中每一个js文件即为一个模块,模块之间需要调用时就需要有导出和导入功能。
module.exports对象是由模块系统自动创建的,在自定义模块中,需要在模块最后通过module.exports或者exports声明这个模块对外暴露了什么,具体地:

  1. 返回一个Object
//app.js
var app = {
    name: 'app',
    version: '1.0.0',
    sayName: function(name){
        console.log(this.name);
    }
}
module.exports = app;


//main.js
const app = require("./app")
app.sayName("111");         //输出111
console.log(app.version);   //输出1.0.0
  1. 返回一个方法
//app.js
function sing(lyrics){
    console.log(lyrics);
}
//给function赋值给一个变量,再导出该变量也是可以的
var dance = function(){
    console.log("来跳个舞");
}

exports.sing = sing;    //当function没有赋给变量时,可以直接导出该函数名
exports.dance= dance;   


//main.js
const app = require("./app");

app.sing("lalalala?"); //输出:lalalala
app.dance();            //输出:来跳个舞

  1. 返回一个变量/常量
//app.js
let aNumber = 666;
const aConst = 888;
var aVar = "哈哈哈";

exports.aNumber = aNumber;
exports.aConst=aConst;
exports.aVar=aVar;


//main.js
console.log(app.aNumber);
console.log(app.aConst);
console.log(app.aVar);

以上可以看出node.js中module.exports和exports的关系与区别:

  • exports是module.exports的一个别名

  • 用法:

    • exports.xxx = yyy;
    • module.exports.xxx = yyy;
      exports的用法完全适用于module.exports
      但是为了各司其职,一般不用第二种写法
    • module.exports = zzz;
      当exports和module.exports同时出现在一个Js文件中时,前者会失效,require该模块的文件只会获得module.exports的内容。
      注意到,modules.exports将会把等号右边的对象当作本模块的唯一导出内容,require时得到的启示就是这个对象的引用,例如:
    //app.js
    function run(){
        console.log("running...");
    }
    module.exports = run;
    
    //main.js
    const app = require("./app");
    app();      //输出:running...
    
    //这里拿到的app 其实就是app.js中被导出的run这个函数的引用
    

在引入了class这个概念以后,模块可以导出一个类,即导出一个构造函数。

    //user.js
    class User{
        constructor(username,password){
            this.username=username;
            this.password=password;
        }
        printInfo(){
            console.log(this.username+" --- "+this.password);
        }
    }

    //导出这个类
    module.exports = User;

在ES6之前还没有class的概念,js中的面向对象是通过function+prototype实现的,一个类按照以下写法导出:

//user.js
//构造函数:
function User(username,password){
    //公共成员
    this.username = username;
    this.password = password;
    //私有成员
    var privateInfo = "我是隐私";
    //公有方法 外界仅能通过公有方法访问该类实例的私有成员
    this.showPrivateInfo = function(){
        console.log(privateInfo);
    };
}

//原型构造:
User.prototype = {
    //静态属性:
    ClazzInfo:{info:"这是User类",shape:"square"},
    setUsername:function(username){
        this.username=username;
    },
    getUsername:function() {
        return this.username
    },

    setPassword:function(password){
        this.password=password;
    },
    getPassword:function(){
        return this.password
    }
}
module.exports = User;

//main.js
var User = require("./user");
var newUser = new User("杨二狗","123456");

通过this构造出来的属性是公有属性,而在构造函数内用var声明的变量则是私有变量。
通过Clazz.prototype声明的变量是该类所有对象公用的,可以理解为Java中的静态变量,当变量是对象类型时,该属性为引用属性:

//user.js
User.prototype = {
    //静态属性:
    ClazzInfo:{info:"这是User类",shape:"square"},
    ....
}

//main.js
var newUser = new User("杨二狗","123456");
var newUser2 = new User("杨二狗22","12345622");
//{ info: '这是User类', shape: 'square' }
console.log(newUser.ClazzInfo); 
newUser.ClazzInfo.shape="round";
//{ info: '这是User类', shape: 'round' }
console.log(newUser2.ClazzInfo);

//可见通过prototype定义的变量是该类对象共享的
//然而该类对象对静态变量的访问是通过引用的,如果直接修改ClazzInfo的指向,不会影响其他对象共享的静态变量
newUser.ClazzInfo = "新的info"; 
console.log(newUser.ClazzInfo); //新的info
console.log(newUser2.ClazzInfo);//{ info: '这是User类', shape: 'round' }

以上就是ES6以前js对的实现方法,通常是将构造函数的方法原型构造结合使用来最大限度实现类的概念。

var和let的区别

  1. 全局作用域中
    二者在全局作用域中被定义时,非常相似,区别仅在于var声明的变量可以通过全局对象window的属性访问到,而let不可以(这里说的window指js在浏览器中执行时的全局对象)

    let bar = 'hehe';
    var baz = 'lalala';
    
    console.log(window.bar);   //undefined
    console.log(window.baz);   //输出lalala
    
  2. 函数作用域和块作用域

    • var声明的变量在整个函数作用域内都可见
    • let声明的变量仅在当前块作用域内可见
        (function foo(){
            //↓是一个块作用域 深度为1
            {
                //↓是另一个块作用域 深度为2
                {
                    var aVar = 123;
                    let aLet = 456;
                }
                //↑是另一个块作用域
                console.log(aVar);  //123 是可见的
                console.log(aLet);  //ReferenceError: aLet is not defined
            }
            //↑是一个块作用域
            console.log(aVar);  //123 是可见的
            
        })()
        //↓ 已经离开了aVar声明的函数作用域 它不可见
        //IDE报错:Unresolved variable or type aVar
        //执行报错:ReferenceError: aVar is not defined
        console.log(aVar);
    
  3. 重新声明

    • var允许在同一个作用域内重复声明,而let不允许
        let me  = 'foo';
        let me  = 'bar'; //SyntaxError: Identifier 'me' has already been declared
    
        var me = 'foo';
        var me = 'bar'; //这里me被替代了,是可以重复声明的
    
  4. 变量提升

    引擎在执行js时会将作用域中的变量进行预解析,把var和function定义的变量提升到作用域的头部进行声明。

    • var定义的变量提升到头部后会立即赋值为undefined
    • let和const定义的变量不会提升

    在这样的情况下,如果代码中在声明一个变量之前就使用它,那么

        (function() {
    
        console.log(varT); //输出undefined
    
        console.log(letT); //直接报错:ReferenceError: letT is not defined
    
        
        var varT = 'test var OK.';
    
        let letT = 'test let OK.';
    
        }());
    

async&await 和 promise机制

javascript的异步编程基于回调函数,如果需要让多个异步任务按一定先后顺序执行,就必然导致回调函数的嵌套,随即产生回调地狱
为了解决这个问题,ES6中引入了

promise机制

  • promise的本质是一个装载着将来某时才能产生的结果的容器。

  • 语法:

    const promise = new Promise(function (resolve, reject) {
        // 执行异步操作
        if (/*异步操作成功*/){
            // 调用resolve,代表Promise将返回成功的结果
            resolve(value)
        }else {
            // 调用reject,代表Promise将返回错误的结果
            reject(error)
        }
    })
    
    

    当拿到某一个异步任务返回的promise对象时:

        promise
        .then((data)=>{
            //表示promise中的任务执行成功
            //data是可能传来的参数
            //这里可以继续执行某个异步任务并return一个promise对象
        })
        .then((data)=>{
            //如果上一个then中也return了promise对象,那就可以继续链式调用then()
        })
        .catch((err)=>{
            //如果在上述的异步任务执行过程中执行了reject()或者throw出了错误
            //就会直接跳到catch中并捕获错误
        })
    

    如此以来,一层一层嵌套的回调函数,就变成了一个链式调用过程,消除了回调地狱。
    除此之外,Promise还提供了Promise.all()和Promose.race()方法,来处理多个异步任务。

    //前后有两个异步任务  分别返回两个promise对象
    let wake = (time) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
            resolve(`${time / 1000}秒后醒来`)
            }, time)
        })
    }
    
    let p1 = wake(3000)
    let p2 = wake(2000)
    
    Promise.all([p1, p2]).then((result) => {
        console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
    }).catch((error) => {
        console.log(error)
    })
    
    Promise.race([p1, p2]).then((result) => {
        console.log(result)
    }).catch((error) => {
        console.log(error)  
    })
    

    如此,可以将传入Promise.all()的多个异步任务结果按照一个list返回。
    当成功时,list中元素为传入时各个promise对象对应的返回值。
    当任意一个promise被reject,.all()会返回最先被reject的err信息。
    Promise.race()则会返回最先获得的某个promise对象的结果,无论是resolve还是reject。

async&await

  • 这也是一套异步编程的解决方案,async&await关键字可以使得异步函数中的代码像同步代码那样执行,由此一来异步编程看起来就像同步编程。
  • 语法:
        //用async关键字标记包含有异步内容的函数
        async function functionContainsAsyncOperation(){
            let result = await asyncOperation()
            //result即是asyncOperation这个异步函数中返回的结果
        }
    

👆 2021.3.4晚更新

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页