金沙国际官网_金沙国际平台登录

因为这个金沙国际官网_金沙国际平台登录网站与很多的大型澳门赌场都有合作,金沙国际官网_金沙国际平台登录尽职尽责,高效执行,保持好奇心,不断学习,追求卓越,点击进入金沙国际官网_金沙国际平台登录马上体验吧,所以现在也正式地开始了营业。

您的位置:金沙国际官网 > web前端 > static以及继承,前端开发基础

static以及继承,前端开发基础

发布时间:2019-11-07 04:52编辑:web前端浏览(87)

    前端开发基础-JavaScript

    2016/03/22 · CSS · Javascript, 前端基础

    原文出处: icepy   

    这是很久很久之前想写的东西,拖了五六个月,没有动笔,现今补齐,内容有些多,对初学者有用,错误之处,望指出。

    JavaScript实现类的private、protected、public、static以及继承

    2015/08/24 · JavaScript · class, private, protected, 继承

    原文出处: Yorhom’s Game Box   

    我在阅读 NodeJS 文档中读出的19个套路

    2016/11/21 · JavaScript · NodeJS

    原文出处: David Gilbertson   译文出处:王下邀月熊_Chevalier   

    虽然我已经用了三年多的NodeJS,也曾经以为自己对其无所不知。但是我好像从未有安静的坐下来仔细地阅读NodeJS的完整文档。如果有熟悉我的朋友应该知道,我之前已经看了HTML,DOM,Web APIs,CSS,SVG以及ECMAScript的文档,NodeJS是我这个系列的最后一个待翻阅的山峰。在阅读文档的过程中我也发现了很多本来不知道的知识,我觉得我有必要分享给大家。不过文档更多的是平铺直叙,因此我也以阅读的顺序列举出我觉得需要了解的点。

    理解作用域

    理解作用域链是Js编程中一个必须要具备的,作用域决定了变量和函数有权力访问哪些数据。在Web浏览器中,全局执行环境是window对象,这也意味着所有的全局变量或者方法都是window对象的属性或方法。当一个函数在被调用的时候都会创建自己的执行环境,而这个函数中所写的代码就开始进入这个函数的执行环境,于是由变量对象构建起了一个作用域链。

    JavaScript

    var wow = '魔兽世界'; var message = function(){ var _wow = '123'; }

    1
    2
    3
    4
    var wow = '魔兽世界';
    var message = function(){
        var _wow = '123';
    }

    在这个例子中全局环境中包含了两个对象(全局环境的变量对象不算),window.wow和window.message,而这个message函数中又包含了两个对象,它自己的变量对象(其中定义了arguments对象)和全局环境的变量对象。当这个函数开始执行时,message自己的变量对象中定义了_wow,而它的全局环境的变量对象有wow,假设在message中alert一下wow,实际上是message中包含的全局环境的变量对象.wow,于是可以访问。

    JavaScript

    var wow = '123'; var message = function(){ var wow = '456'; }

    1
    2
    3
    4
    var wow = '123';
    var message = function(){
        var wow = '456';
    }

    如果执行message函数alert一下wow,它的作用域是这样开始搜索的,先搜索message自己的变量对象中是否存在wow,如果有就访问并且立马停止搜索,如果没有则继续往上访问它,有wow,则访问并且立马停止搜索,以此类推一直搜索到全局环境上的变量对象,如果这里都没,恭喜你,这里要抛错了。

    JavaScript

    var c = '123'; var message = function(){ var g = '123'; var a = function(){ var d = '123'; } }

    1
    2
    3
    4
    5
    6
    7
    var c = '123';
    var message = function(){
        var g = '123';
        var a = function(){
            var d = '123';
        }
    }

    在这个例子中包含有三个执行环境,全局环境,message的环境,a的环境。从这里可以看出message自身包含两个对象,自己的变量对象和全局环境中的变量对象,而函数a则包含了三个,自身的变量对象,message的变量对象和全局变量对象。

    当开始执行这个函数时,在函数a中可以访问到变量g,那是因为函数a包含了message的变量对象,于是在自身没有开始搜索上一级的变量对象时发现了,于是可以访问。那么访问c的原理也是如此,当自身和上一级的message的变量对象都没有,但是全局变量对象中存在,于是访问成功。

    了解这个作用域,对于Js编程是至关重要的,不然可能会出现,明明想要的预期结果是123,但是变成了456,为什么?那就是因为一级一级的搜索,可能会存在覆盖,或者搜索到别的地方就立即停止搜索了。

    基础知识

    querystring:可以用作通用解析器的模块

    很多时候我们会从数据库或其他地方得到这种奇怪格式的字符串:name:Sophie;shape:fox;condition:new,一般来说我们会利用字符串切割的方式来讲字符串划分到JavaScript Object。不过querystring也是个不错的现成的工具:

    JavaScript

    const weirdoString = `name:Sophie;shape:fox;condition:new`; const result = querystring.parse(weirdoString, `;`, `:`); // result: // { // name: `Sophie`, // shape: `fox`, // condition: `new`, // };

    1
    2
    3
    4
    5
    6
    7
    8
    const weirdoString = `name:Sophie;shape:fox;condition:new`;
    const result = querystring.parse(weirdoString, `;`, `:`);
    // result:
    // {
    //   name: `Sophie`,
    //   shape: `fox`,
    //   condition: `new`,
    // };

    理解引用类型

    引用类型虽然看起来和类很相似,但是它们却是不同的概念,引用类型的值,也就是对象是引用类型的一个实例。在Js中引用类型主要有Object,Array,Date,正则,Function等。

    Object和Function在后面详细复述。

    Array

    在Js中数组可以存储任意的数据,而且它的大小是可以动态调整的类似于OC中的NSMutableArray。创建数组可以使用构造函数的方式也可以使用字面量的形式,另外可以使用concat从一个数组中复制一个副本出来。数组本身提供了很多方法让开发者使用来操作数组。

    • length 数组的长度
    • toString 可以返回一个以,拼接的字符串,相当于是调用了下join(‘,’)
    • join 可以用一个分割符来拼接成一个字符串
    • push 添加一个数据到数组的末端
    • pop 删除数组中的最后一项,有返回值
    • shift 删除数组的第一项,有返回值
    • unshift 添加一个数据到数组的首端
    • reverse 倒序
    • sort 可以传入一个排序的函数
    • slice 可以基于当前数组返回一个新的数组,接收两个参数,返回项的起始位置和结束位置
    • splice 可以传入N个参数,第一个参数表示要删除,插入或则替换的位置,第二个参数表示要删除的项数,第三个到第N个表示要插入或则替换的数据

    Date

    时间对象也是使用非常多的玩意,它是使用GMT时间来描述,而且时间对象是可以直接比对大小的。

    JavaScript

    var date1 = new Date(2015,1,2); var date2 = new Date(2015,1,10); date1 < date2

    1
    2
    3
    var date1 = new Date(2015,1,2);
    var date2 = new Date(2015,1,10);    
    date1 < date2

    常用的方法

    • getTime 获取时间对象的毫秒数
    • setTime 设置时间对象的毫秒数,会改变日期
    • getFullYear 获取时间对象的年(2015)
    • getMonth 获取时间对象的月(需要加1)
    • getDay 获取日期的星期几(0-6)星期天到星期六
    • getDate 获取日期的天数
    • getHours 获取当前日期的小时
    • getMinutes 获取当前日期的分钟数
    • getSeconds 获取当然日期的秒数

    上面看起来都是获取,当然也有设置,只是相应的get置换成set即可。

    正则表达式

    在Js里正则表达式是用RegExp类型来支持的,关于正则可以看看之前写的一篇文章,用python来描述的如何读懂正则。

    Js也支持三种模式,gim,表示全局,不区分大小写,多行。

    一般来说很少有人这么使用var xxx = new RegExp(),而是用字面量的方式,比如var xx = /[bc]/gi;像用的比较多的方法有exec用于捕获包含第一个匹配项的数组,没有则返回null。test,用于判断,如果匹配返回true,不匹配返回false。

    处理字符串

    在Js中还有一种叫做包装类型的玩意,正因为此所以处理一些基本数据类型,比如字符串时,有很多方法可以使用。

    • concat 可以将一个或者多个字符串拼接起来,返回一个新的字符串
    • slice 接收两个参数,起始位置和结束位置,返回一个新的字符串
    • substr和substring和slice一样,唯一的不同是substr第二个参数是返回字符串的个数
    • indexOf 从头开始查询字符串,存在会返回它所在的位置,没有返回-1
    • lastIndexOf 从最后开始查询字符串
    • toUpperCase 转大写
    • toLowerCase 转小写
    • match 正则表达式使用跟exec一样
    • search 正则表达式使用,查询到返回一个位置,没有返回-1
    • replace 替换,第一个参数可以是正则表达式也可以是字符串,第二个参数是要替换的字符串
    • localeCompare比较字符串,如果字符串相等返回0,如果字符串的字母排在参数字符串之前,返回负数,如果是之后,返回正数。

    JavaScript中的类

    JavaScript实际上是一种弱类型语言,与C++和Java等语言不同。因此,在JavaScript中,没有强调类(class)这一概念,但实际运用中,类还是很重要的,比如写一款游戏,如果我们不停地调用函数来完成创建角色,移动角色的话,那会是什么样的呢?可能会出现非常多的重复代码,因此我们需要一个类来统一这些代码。所谓的类,就是把程序中的代码分类,比如说游戏中的关于角色的代码算作一类,游戏背景算作一类,游戏特效又是一类。这样一来,我们对类进行操作,就不会使代码显得很凌乱,冗杂。虽然Js是弱类型语言,但是也提供了类这一概率。
    定义Js中的类,实际上用的是function,总所周知,这个语法其实是用来定义函数的。不用于定义函数的是,我们可以在function中通过this.xxx的方式来定义属性和方法。比如说:

    JavaScript

    function People () { this.name = "Yorhom"; this.getName = function () { return this.name }; }

    1
    2
    3
    4
    5
    6
    7
    function People () {
        this.name = "Yorhom";
     
        this.getName = function () {
            return this.name
        };
    }

    使用的时候使用new

    JavaScript

    var yorhom = new People(); // "Yorhom" alert(yorhom.getName());

    1
    2
    3
    var yorhom = new People();
    // "Yorhom"
    alert(yorhom.getName());

    可以看到,这样就可以使用到我们定义的类和类中的方法了。
    也许你会问this.xxx只能定义公有属性和方法,那私有属性和方法怎么办呢?这个可以用到js闭包的知识来解决:

    JavaScript

    function People () { this.name = "Yorhom"; var age = 16; this.getName = function () { return this.name }; this.getAge = function () { return age; }; } var yorhom = new People(); // undefined alert(yorhom.age); // 16 alert(yorhom.getAge());

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function People () {
        this.name = "Yorhom";
     
        var age = 16;
     
        this.getName = function () {
            return this.name
        };
     
        this.getAge = function () {
            return age;
        };
    }
     
    var yorhom = new People();
    // undefined
    alert(yorhom.age);
    // 16
    alert(yorhom.getAge());

     

    可以看到,这里的age就是一个私有属性了。

    V8 Inspector

    --inspect参数运行你的Node应用程序,它会反馈你某个URL。将该URL复制到Chrome中并打开,你就可以使用Chrome DevTools来调试你的Node应用程序啦。详细的实验可以参考这篇文章。不过需要注意的是,该参数仍然属于实验性质。
    图片 1

    函数

    • Function

    说起来Js的核心是什么?那就是函数了。对于函数主要是理解它的几个概念。

    • 它可以当值来传递,没有重栽。
    • 声明的时候,比如function a(){} var a = function(){} 执行时会有区别
    • 函数内部的参数arguments包含了传入的所有参数
    • this,表示在这个函数内的作用域,以及prototype

    JavaScript中的prototype

    上面的代码美中不足的地方就是,如果一个类有很多方法,同时用到这个类的地方又有很多(也就是new出来的对象有很多),那么用上面的代码就会出现内存占用过剩的问题。问题的根本原因在于,每次实例化一个对象,这个类就会执行构造器里的代码(以People类为例就是function People () {…}执行的代码),因此每当这个类被实例化的时候,这些方法和属性就会被拷贝到实例化出来的对象中。这样一来,就会造成“吃”内存的现象。
    于是js中的prototype就诞生了。prototype的作用通常是给一个类添加一系列常量或者方法。 每当一个类被实例化之后,实例化出来的对象会自动获取类的prototype中定义的方法和属性。只不过这里的获取类似于C++里面的引用,不会在内存里对这些方法和属性进行复制,而是指向这些方法和属性。示例:

    JavaScript

    function People () { this.name = "Yorhom"; } People.prototype.getName = function () { return this.name; }; var yorhom = new People(); // "Yorhom" alert(yorhom.getName());

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function People () {
        this.name = "Yorhom";
    }
     
    People.prototype.getName = function () {
        return this.name;
    };
     
    var yorhom = new People();
    // "Yorhom"
    alert(yorhom.getName());

     

    这种方法虽然可以节约内存,但是,美中不足的是,无法定义私有属性。

    nextTick 与 setImmediate的区别

    这两货的区别可能光从名字上还看不出来,我觉得应该给它们取个别名:

    • process.nextTick()应该为process.sendThisToTheStartOfTheQueue()
    • setImmediate应该为sendThisToTheEndOfTheQueue()

    再说句不相关的,React中的Props应该为stuffThatShouldStayTheSameIfTheUserRefreshes,而State应该为stuffThatShouldBeForgottenIfTheUserRefreshes

    理解匿名函数和闭包

    匿名函数又叫拉姆达函数,主要是在把函数当值传递的时候用,或者是把函数当返回值,比如:

    JavaScript

    function d(callback){ callback(); } d(function(){ alert('123') }); //或者 function b(){ return function(){ alert('123'); } } var g = b(); g();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function d(callback){
        callback();
    }
    d(function(){
        alert('123')
    });
     
    //或者
     
    function b(){
        return function(){
            alert('123');
        }
    }
    var g = b();
    g();

    其实第二种方式跟闭包的意义一样了,所谓的闭包书面的解释是可以访问另一个函数作用域内变量的函数,稍微改写一下可能会更明显。

    JavaScript

    function b(){ var name = '123'; return function(){ alert(name); } } var g = b(); g();

    1
    2
    3
    4
    5
    6
    7
    8
    function b(){
        var name = '123';
        return function(){
            alert(name);
        }
    }
    var g = b();
    g();

    从这里可以看出来return的函数可以访问到name,而外部却不行,这个返回值的函数就可以理解为闭包。理解闭包还可以看一个经典的求值的例子。

    JavaScript

    function save_i(){ var a = []; for(var i = 0;i<10;i++){ a[i] = function(){ return i; } } return a; } var c = save_i(); for(var i = 0;i<10;i++){ alert(c[i]()); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function save_i(){
        var a = [];
        for(var i = 0;i<10;i++){
            a[i] = function(){
                return i;
            }
        }
        return a;  
    }
     
    var c = save_i();
    for(var i = 0;i<10;i++){
        alert(c[i]());
    }

    从这个例子上来看,我们想得到的结果是10次循环a[i]保存着一个闭包,然后alert出从0到10,但是结果很出人意料,全部是10,为什么?哪里理解的不对呢?a[i]明明是内部函数,然后让它访问另外一个函数作用域内的变量i。

    个人觉得可以这样去分析问题,在客户端执行Js时有一个全局执行环境,指向的是window对象。而所谓的对象也就是引用类型,实际上在后台执行环境中,它就是一个指针。

    回到Js当代码在执行的时候,会创建变量对象并且构建一个作用域链,而这个对象保存着当前函数可以访问的对象。

    JavaScript

    window ->save_i ->this|argument ->a ->i ->看不见的a[0]-a[10] ->a[0]function(){} ->i ->c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    window
        ->save_i
            ->this|argument
            ->a
            ->i
            ->看不见的a[0]-a[10]
            ->a[0]function(){}
                ->i
        ->c

    上述的i和a[0]里的i是同一个i,那么结果就是10。

    进一步处理

    JavaScript

    function save_i(){ var a = []; for(var i = 0;i<10;i++){ a[i] = function(k){ return function(){ return k; }; }(i) } return a; } var c = save_i(); for(var i = 0;i<10;i++){ console.log(c[i]()); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function save_i(){
        var a = [];
        for(var i = 0;i<10;i++){
            a[i] = function(k){
                return function(){
                    return k;
                };
            }(i)
        }
        return a;  
    }
     
    var c = save_i();
    for(var i = 0;i<10;i++){
        console.log(c[i]());
    }

    接着按上面的节奏来分析

    JavaScript

    window ->save_i ->this|argument ->a ->i ->看不见的a[0]-a[10] ->a[0]function(){} ->k ->function(){} ->k ->c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    window
        ->save_i
            ->this|argument
            ->a
            ->i
            ->看不见的a[0]-a[10]
            ->a[0]function(){}
                ->k
                ->function(){}
                    ->k
     
        ->c

    什么是传参?按值传递,相当于是在那个立即执行的函数中创建了一个新的地址和空间,虽然值是一样的,但是每一个k又是不同的,所以得到的结果正好满足了我们的预期。

    本来正常情况下save_i执行完毕后就要销毁,但是内部的闭包被包含在这个作用域内了,所以save_i没法销毁,从这里可以看的出来闭包会带来内存的问题,因为用完之后没法销毁,如果不注意的话。

    那么用完之后只能设置为null来解除引用,等着自动销毁把内存回收。

    类的继承

    Javascript没有提供继承的函数,所以只有自己写了。这里借用lufylegend.js中的继承方法向大家展示如何实现继承:

    JavaScript

    function base (d, b, a) { var p = null, o = d.constructor.prototype, h = {}; for (p in o) { h[p] = 1; } for (p in b.prototype) { if (!h[p]) { o[p] = b.prototype[p]; } } b.apply(d, a); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function base (d, b, a) {
        var p = null, o = d.constructor.prototype, h = {};
     
        for (p in o) {
            h[p] = 1;
        }
        for (p in b.prototype) {
            if (!h[p]) {
                o[p] = b.prototype[p];
            }
        }
     
        b.apply(d, a);
    }

    这里的base就是继承函数了。继承函数的原理莫过于复制类的方法和属性。因此,只要做到这点,就可以实现类的继承了。可以在上面的代码中看见,我们通过遍历prototype来获取原型链中定义的方法和属性。通过apply调用父类的构造器进行构造器中属性和方法的复制。使用示例:

    JavaScript

    function People () { this.name = "Yorhom"; } People.prototype.getName = function () { return this.name; }; function Student () { base(this, People, []); } var yorhom = new Student(); // "Yorhom" alert(yorhom.getName());

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function People () {
        this.name = "Yorhom";
    }
     
    People.prototype.getName = function () {
        return this.name;
    };
     
    function Student () {
        base(this, People, []);
    }
     
    var yorhom = new Student();
    // "Yorhom"
    alert(yorhom.getName());

     

    Server.listen 可以使用Object作为参数

    我更喜欢命名参数的方式调用函数,这样相较于仅按照顺序的无命名参数法会更直观。别忘了Server.listen也可以使用某个Object作为参数:

    JavaScript

    require(`http`) .createServer() .listen({ port: 8080, host: `localhost`, }) .on(`request`, (req, res) => { res.end(`Hello World!`); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    require(`http`)
      .createServer()
      .listen({
        port: 8080,
        host: `localhost`,
      })
      .on(`request`, (req, res) => {
        res.end(`Hello World!`);
      });

    不过这个特性不是表述在http.Server这个API中,而是在其父级net.Server的文档中。

    Object

    JavaScript的所有对象都衍生于Object对象,所有对象都继承了Object.prototype上的方法和属性,虽然它们可能会被覆盖,熟悉它对于编程能起到很大的作用,也能比较深刻的了解JavaScript这门语言。

    Object

    创建一个对象可以使用new,也可以使用快速创建的方式:

    JavaScript

    var _object = {};

    1
    var _object = {};

    _object对象中就可以使用Object.prototype中所有的方法和属性,虽然看起来它是空的。说到这里在编程中常常有一个非常有用的需求,如何判断一个对象是空对象。

    这是zepto中的判断一个对象是否是空对象,常常使用:

    JavaScript

    $.isEmptyObject = function(obj) { var name for (name in obj) return false return true }

    1
    2
    3
    4
    5
    $.isEmptyObject = function(obj) {
            var name
            for (name in obj) return false
            return true
    }

    也顺便看了下jQuery原理是一模一样的:

    JavaScript

    isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }

    1
    2
    3
    4
    5
    6
    7
    isEmptyObject: function( obj ) {
        var name;
        for ( name in obj ) {
            return false;
        }
        return true;
    }

    使用in操作符来实现,它不会遍历到父原型链。

    constructor返回一个指向创建了该对象的函数引用,这个东西主要是可以用来识别(类)到底是指向哪里的。

    defineProperty直接在一个对象上定义一个新属性,非常适合用于动态构建,传入三个参数[动态添加对象的目标对象,需要定义或被修改的属性名,需要定义的对象],在第三个参数中可以有些属性来表示是否继承(proto),要不要定义get,set方法,enumerable是否可枚举。

    defineProperties跟上述defineProperty一样,但是它可以添加多个。

    getOwnPropertyNames返回一个由指定对象的所有属性组成的数组

    keys返回一个数组包括对象所有的属性(可枚举)

    keys是经常会用到的一个属性,它只能包可枚举的,如果想获取一个对象的所有属性包括不枚举的,那么使用getOwnPropertyNames。

    hasOwnProperty用于判断某个对象是否包含有自身的属性,这个方法常常用于检测对象中的属性是否存在,它只检测自身,对于继承过来的都是false,这一点是非常重要的理解。

    isPrototypeOf 用于检测一个对象是否在另一个对象的原型链上,比如有两个对象是互相交互的,常常会使用它来进行检测。

    propertyIsEnumerable这个方法也比较重要,返回一个布尔值,检测一个对象的自身属性是否可以枚举

    可枚举的理解,也就是对象的属性可枚举,它的属性值不可以修改,但是在Js中它有自己的定义,引擎内部看不见的该属性的[[Enumerable]]特性为true,那么就是可枚举的。基本上把一个普通对象可以看做是一个枚举类型,比如var color = {‘red’:1},red是可以修改的,但是red是可枚举的,但是如果是继承过来的属性,propertyIsEnumerable是返回false的,它还有一个特点,就是自身。

    如果要定义不可枚举的属性,那就要使用defineProperty方法了,目前不能用对象直接量或者构造函数定义出来。

    JavaScript

    var obj = {name: 'jack', age:23} Object.defineProperty(obj, 'id', {value : '123', enumerable : false });

    1
    2
    var obj = {name: 'jack', age:23}
    Object.defineProperty(obj, 'id', {value : '123', enumerable : false });

    静态属性和方法的定义

    静态属性和方法以及静态类在js中的定义非常简单,先来看静态类:

    JavaScript

    var StaticClass = {};

    1
    var StaticClass = {};

    这么写不是在定义一个Object吗?是的,不错,不过js中的静态类也是可以这样定义的。如果要添加静态类中的方法和属性,就可以这么写:

    JavaScript

    var StaticClass = { id : 5, sayHello : function () { alert("Hello"); } };

    1
    2
    3
    4
    5
    6
    var StaticClass = {
        id : 5,
        sayHello : function () {
            alert("Hello");
        }
    };

    如果是要向类中添加静态属性或者方法,可以采用这种写法:

    JavaScript

    function People () { this.name = "Yorhom"; } People.prototype.getName = function () { return this.name; }; People.TYPE = "people"; People.sayHello = function () { alert("Hello"); };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function People () {
        this.name = "Yorhom";
    }
     
    People.prototype.getName = function () {
        return this.name;
    };
     
    People.TYPE = "people";
    People.sayHello = function () {
        alert("Hello");
    };

     

    相对地址

    你传入fs模块的距离可以是相对地址,即相对于process.cwd()。估计有些人早就知道了,不过我之前一直以为是只能使用绝对地址:

    JavaScript

    const fs = require(`fs`); const path = require(`path`); // why have I always done this... fs.readFile(path.join(__dirname, `myFile.txt`), (err, data) => { // do something }); // when I could just do this? fs.readFile(`./path/to/myFile.txt`, (err, data) => { // do something });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const fs = require(`fs`);
    const path = require(`path`);
    // why have I always done this...
    fs.readFile(path.join(__dirname, `myFile.txt`), (err, data) => {
      // do something
    });
    // when I could just do this?
    fs.readFile(`./path/to/myFile.txt`, (err, data) => {
      // do something
    });

    深拷贝与浅拷贝

    关于拷贝的问题,主要分为深拷贝和浅拷贝,但是如果从空间分配上来说JavaScript的拷贝不应该算是深拷贝,比如:

    JavaScript

    var d = {}; for(k in a){ d[k] = a[k]; } return d;

    1
    2
    3
    4
    5
    var d = {};
    for(k in a){
        d[k] = a[k];
    }
    return d;

    今天突然想到了这么一个问题,在C语言中,所谓的拷贝,就是分两种情况,一种是把指针地址拷贝给另外一个变量,虽然也开辟的了一个内存空间,在栈上也存在着一个地址,我对这个变量进行修改,同一个指针是会改变其值的,这种拷贝叫浅拷贝。另外一种情况,直接开辟一个新空间,把需要复制的值都复制在这个新的空间中,这种拷贝叫中深拷贝。

    如果看到上述的一段Js代码,很多人说它是浅拷贝,假设传入一个a对象,拷贝完成之后返回一个d,当我修改返回对象的值时并不能同时修改a对象,于是,在这里我有一个很大的疑问,在Js中到底什么是浅拷贝,什么是深拷贝的问题?

    这一点上感觉Js真的很奇葩,如果在开发iOS中,不可变对象copy一下,依然是不可变,所以是浅拷贝,拷贝了指针变量中存储的地址值。如果是可变对象copy一下,到不可变,空间变化了,包括不可变mutableCopy到不可变,空间依然变化了,所以是深拷贝。但是JavaScript中对于这一点要考虑一种情况,值类型,和引用类型,这个基础知识,我相信大家都非常清楚。数字,字符串等都是值类型,object,array等都是引用类型。

    JavaScript

    var a = [1,2,3]; var b = a; b.push(4); console.log(a); //[1,2,3,4] var numb = 123; var _numb = numb; _numb = 567; console.log(numb); //123

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a = [1,2,3];
    var b = a;
     
    b.push(4);
    console.log(a); //[1,2,3,4]
     
    var numb = 123;
    var _numb = numb;
    _numb = 567;
     
    console.log(numb); //123

    从这个例子中可以看的出来,它们使用的都是=符号,而数组a发生了变化,numb数字却没有发生变化。那么从这里,可以有一个总结,所谓了深拷贝,浅拷贝的问题,应该针对的是有多个嵌套发生的情况。不然假设是这样的情况,还能叫浅拷贝么?

    JavaScript

    var object = {"de":123}; var o = copy(object); o.de = 456; console.log(object) //{"de":123}

    1
    2
    3
    4
    var object = {"de":123};
    var o = copy(object);  
    o.de = 456;
    console.log(object) //{"de":123}

    明显对象o中的de属性修改并没有影响到原始对象,一个对象中的属性是一个字符串,如果从内存空间的角度上来说,这里明显是开辟了新的空间,还能说是浅拷贝么?那么针对另外一种情况。

    JavaScript

    var object = { "de":{ "d":123 } } var o = deepCopy(object); o.de.d = "asd";

    1
    2
    3
    4
    5
    6
    7
    var object = {
        "de":{
            "d":123
        }
    }
    var o = deepCopy(object);
    o.de.d = "asd";

    如果一个对象中的第一层属性,不是值类型,只单层循环,这样来看的话确实是一个浅拷贝,因为在Js中引用类型用=赋值,实际上是引用,这样说的通。所以,深拷贝,还需要做一些处理,把object,array等引用类型识别出来,深层递归到最后一层,一个一个的拷贝。

    JavaScript

    var deepCopy = function(o){ var target = {}; if(typeof o !== 'object' && !Array.isArray(o)){ return o; } for(var k in o){ target[k] = deepCopy(o[k]); } return target; }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var deepCopy = function(o){
        var target = {};
        if(typeof o !== 'object' && !Array.isArray(o)){
            return o;
        }
        for(var k in o){
            target[k] = deepCopy(o[k]);
        }
        return target;
    }

    思路是如此,这个例子只考虑了两种情况,对象和数组,为了验证这样的思路,最后的结果与预期是一样的。

    JavaScript

    var _copy = { 'object':{ 'name':'wen' }, 'array':[1,2] } var h = deepCopy(_copy); h.object.name = 'lcepy'; h.array[1] = 8; console.log(h); console.log(_copy);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var _copy = {
        'object':{
            'name':'wen'
        },
        'array':[1,2]
    }
    var h = deepCopy(_copy);
    h.object.name = 'lcepy';
    h.array[1] = 8;
    console.log(h);
    console.log(_copy);

    实现一个功能丰富的类

    我们在上文中提到了,节省内存和定义私有属性两者无法兼得,是啊,和“鱼和熊掌不可兼得”是一个道理,在通常的使用过程中,我们需要对这两项进行取舍。但是现在这个年代,哪有不可兼得的呢?鱼和熊掌不能同时吃?当然不行……因为吃熊掌是违法的(有待考证)?不过至少鸡和鱼是可以同时吃的吧。
    由于js没有实现私有属性的定义,所以这其实是一个没有头绪的工作,因为在标准的做法中,我们除了闭包可以阻止外部访问,没有别的办法了。所以这里我们要用点歪门邪道的方法了。

    Path Parsing:路径解析

    之前我一直不知道的某个功能就是从某个文件名中解析出路径,文件名,文件扩展等等:

    JavaScript

    myFilePath = `/someDir/someFile.json`; path.parse(myFilePath).base === `someFile.json`; // true path.parse(myFilePath).name === `someFile`; // true path.parse(myFilePath).ext === `.json`; // true

    1
    2
    3
    4
    myFilePath = `/someDir/someFile.json`;
    path.parse(myFilePath).base === `someFile.json`; // true
    path.parse(myFilePath).name === `someFile`; // true
    path.parse(myFilePath).ext === `.json`; // true

    面向对象

    面向对象的语言有一个非常明显的标志:类,通过类来创建任意多个具有相同属性和方法的对象,可惜的是Js里没有这样的概念。

    但是Js有一个特性:一切皆是对象。

    聪明的开发者通过这些特性进行摸索,于是迂回发明了一些程序设计,以便更好的组织代码结构。

    JavaScript Set/Get访问器

    什么是set/get访问器呢?如果你熟悉python,那么你可以理解为@property@xxx.setter,但是简陋的js里也有?当然有,只不过是ES5的标准,可以采用这种写法:

    JavaScript

    Object.defineProperty(this, "name", { get : funtion () { return name; }, set : function (v) { name = v; } });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Object.defineProperty(this, "name", {
        get : funtion () {
            return name;
        },
     
        set : function (v) {
            name = v;
        }
    });

     

    具体有什么用呢?大致就是this.name属性在被获取的时候调用get访问器,在被更改值的时候调用set
    你可以从上面的代码了解大致的写法,不过如果你想深究,可以参考这篇文章:

    注意以上的这种用法会有兼容性问题,浏览器支持情况如下:

    PC端

    Firefox Google Chrome Internet Explorer Opera Safari
    4.0 5 9 11.6 5.1

    移动端

    Firefox Mobile Android IE Mobile Opera Mobile Safari Mobile
    4.0 Yes 9 11.5 Yes

    来自: https://developer.mozilla.org/…/defineProperty#Browser_compatibility

    Logging with colors

    别忘了console.dir(obj,{colors:true})能够以不同的色彩打印出键与值,这一点会大大增加日志的可读性。

    工厂模式

    主要是用来解决有多个相同属性和方法的对象的问题,可以用函数来封装特定的接口来实现

    JavaScript

    var computer = function(name,version){ return { 'name':name, 'version':version, 'showMessage':function(){ alert(this.name); } } } var test = computer('apple','11.1'); test.showMessage();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var computer = function(name,version){
        return {
            'name':name,
            'version':version,
            'showMessage':function(){
                alert(this.name);
            }
        }
    }
    var test = computer('apple','11.1');
    test.showMessage();

    如何“歪门邪道”地做到禁止访问私有和保护属性?

    这是个比较头疼的问题,正如本节开篇所说,我们在常规开发下,只能通过闭包来阻止某变量的访问。可是如果你使用了prototype,那么闭包这条路就走不通了。在这种情况下,我们的Object.defineProperty就出场了。我们知道,通过这个函数可以设定获取属性时返回的值,也可以设定更改属性时设置的值。有了这个函数,我们可以随时跟踪到某个属性是不是在被获取,或者是不是在被更改。我们还需要一个开关,我们在类内部的方法调用时,把这个开关打开,表明是在内部运行,方法调用结束后将开关关闭,表明回到外部运行状态。有了这两个状态,我们就可以跟踪privateprotected属性和方法了,一旦他们在开关关闭的时候被使用,就终止这个属性或方法的获取或设置。
    于是乎,大难题就快解决了。

    使用setInterval执行定时任务

    我喜欢使用setInterval来定期执行数据库清理任务,不过默认情况下在存在setInterval的时候NodeJS并不会退出,你可以使用如下的方法让Node沉睡:

    JavaScript

    const dailyCleanup = setInterval(() => { cleanup(); }, 1000 * 60 * 60 * 24); dailyCleanup.unref();

    1
    2
    3
    4
    const dailyCleanup = setInterval(() => {
      cleanup();
    }, 1000 * 60 * 60 * 24);
    dailyCleanup.unref();

    构造函数模式

    我们知道像原生的构造函数,比如Object,Array等,它们是在运行时自动出现在执行环境中的。因此,为了模仿它,这里也可以通过一个普通的函数,并且new出一个对象,这样就成为了自定义的构造函数,也可以为他们添加自定义的属性和方法。

    但是这样的构造函数有一个缺陷,就是每个方法都会在每个实例上创建一次,因为每次创建都需要分配内存空间,但是有时候这样的特性还是有用的,主要是要控制它们,在不使用的时候释放内存。

    JavaScript

    var Computer = function(name,version){ this.name = name; this.version = version; this.showMessage = function(){ alert(this.name); } } var apple = new Computer('apple',2014); var dell = new Computer('dell',2010); apple.showMessage(); dell.showMessage();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var Computer = function(name,version){
        this.name = name;
        this.version = version;
        this.showMessage = function(){
            alert(this.name);
        }
    }
    var apple = new Computer('apple',2014);
    var dell = new Computer('dell',2010);
    apple.showMessage();
    dell.showMessage();

    像apple,dell是通过Computer实例化出来的不同的对象,但是它们的constructor都是指向Computer的。这里也可以使用instanceof来对(对象)进行检测。

    在书写上构造函数跟其他函数是没有什么区别的,主要的区别还是在使用上,构造函数需要使用new操作符。

    其实这样的书写,已经跟类没有什么区别了,表面上来看,而构造函数我个人更倾向于一个类的某个静态方法。

    开源库件jpp.js

    秉着这个歪门邪道的思想,我把这个功能封装到jpp.js这个库件中,库件的github地址如下:

    当然这个库件不限于创建一个类,还可以实现函数的重载等。目前库件还处于开发阶段,欢迎各位提交建议。

    Use Signal Constants

    如果你尝试在NodeJS中杀死某个进程,估计你用过如下语法:

    JavaScript

    process.kill(process.pid, `SIGTERM`);

    1
    process.kill(process.pid, `SIGTERM`);

    这个没啥问题,不过既然第二个参数同时能够使用字符串与整形变量,那么还不如使用全局变量呢:

    JavaScript

    process.kill(process.pid, os.constants.signals.SIGTERM);

    1
    process.kill(process.pid, os.constants.signals.SIGTERM);

    原型模式

    说到原型模式就不得不提一提关于指针的问题,因为每一个函数都有一个prototype属性,而这个属性是一个指针,指向一个对象。

    C语言描述指针,这个在iOS开发中非常重要

    比如我先定义一个int类型的指针变量和一个普通的int类型数据,然后给指针变量赋值。

    JavaScript

    int *p; int pp = 123; p = &pp; *p = 999; printf('%d',pp);

    1
    2
    3
    4
    5
        int *p;
        int pp = 123;
        p = &pp;
        *p = 999;
        printf('%d',pp);

    *是一个特殊符号用于标明它是一个指针变量。

    &是地址符

    分析这个就要说到栈内存和堆内存了,比如*p在栈内存中分配了一个地址假设是ff22x0,它还没有空间。而pp存在一个地址ff23x0,并且分配了一个空间存储着123,这个地址是指向这个空间的。

    p = &pp 这样的赋值操作,也就是把ff23x0取出来,并且给p分配一个空间把ff23x0存储进去,并且ff22x0指向这个空间。

    *p = 999 从这里就可以看出来p操作的是地址,而这个地址不就是ff23x0么,于是pp成了999。

    所谓的指针也就是存储着地址的变量。

    回到原型上,如果每一个函数中的

    使用jpp.js创建一个类

    JavaScript

    var People = jpp.class({ extends : null, private : { id : null, hobby : null }, protected : { money : null, phoneNumber : null }, public : { firstName : null, lastName : null, age : null, birthday : null, occupation : null, constructor : function (name, id) { if (name) { var nameArray = name.split(" "); this.firstName = nameArray[0]; this.lastName = nameArray[1]; } if (id) { this.id = id; } }, setBirthday : function (date) { if (date) { this.birthday = date; } }, getBirthday : function () { return this.birthday; }, askForId : function () { return this.id; }, findHobby : function () { return this.hobby; } }, static : { OCCUPATION_PROGRAMMER : "programmer", OCCUPATION_ARTIST : "artist", OCCUPATION_MUSICIAN : "musician", OCCUPATION_STUDENT : "student" } }); var peter = new People("Peter Wong", 543232123565); peter.occupation = People.OCCUPATION_PROGRAMMER; peter.setBirthday("19980727"); // result: Peter alert(peter.firstName); // result: 19990727 alert(peter.getBirthday()); // result: 51092028 alert(peter.askForId()); // result: null alert(peter.findHobby()); // result: programmer alert(peter.occupation); // error alert(peter.id);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    var People = jpp.class({
        extends : null,
        private : {
            id : null,
            hobby : null
        },
        protected : {
            money : null,
            phoneNumber : null
        },
        public : {
            firstName : null,
            lastName : null,
            age : null,
            birthday : null,
            occupation : null,
     
            constructor : function (name, id) {
                if (name) {
                    var nameArray = name.split(" ");
     
                    this.firstName = nameArray[0];
                    this.lastName = nameArray[1];
                }
     
                if (id) {
                    this.id = id;
                }
            },
     
            setBirthday : function (date) {
                if (date) {
                    this.birthday = date;
                }
            },
     
            getBirthday : function () {
                return this.birthday;
            },
     
            askForId : function () {
                return this.id;
            },
     
            findHobby : function () {
                return this.hobby;
            }
        },
        static : {
            OCCUPATION_PROGRAMMER : "programmer",
            OCCUPATION_ARTIST : "artist",
            OCCUPATION_MUSICIAN : "musician",
            OCCUPATION_STUDENT : "student"
        }
    });
     
    var peter = new People("Peter Wong", 543232123565);
    peter.occupation = People.OCCUPATION_PROGRAMMER;
     
    peter.setBirthday("19980727");
     
    // result: Peter
    alert(peter.firstName);
    // result: 19990727
    alert(peter.getBirthday());
    // result: 51092028
    alert(peter.askForId());
    // result: null
    alert(peter.findHobby());
    // result: programmer
    alert(peter.occupation);
    // error
    alert(peter.id);

     

    对上面的代码进行分析:
    使用jpp.class函数创建一个类,函数的参数是一个Object,这个Object可添加的属性如下:

    • extends 继承时的父类
    • private 装载私有属性,里面定义的成员外部不可使用且不能继承给子类
    • protected 装载保护属性,里面定义的成员外部不可使用但可以继承给子类
    • public 装载公有属性
    • static 装载静态方法和属性

    在创建类的过程中,在public中添加constructor方法初始化构造器,this.super可访问父类构造器。

    运行代码,可以看到浏览器正常运行前5个alert,而最后一个运行的时候浏览器报错:

    图片 2

    具体的实现过程有点复杂,不过原理在上文已经详细讲述了。代码可以在github里参看,欢迎各位研究。

    2 赞 6 收藏 评论

    图片 3

    IP Address Validation

    NodeJS中含有内置的IP地址校验工具,这一点可以免得你写额外的正则表达式:

    JavaScript

    require(`net`).isIP(`10.0.0.1`) 返回 4 require(`net`).isIP(`cats`) 返回 0

    1
    2
    require(`net`).isIP(`10.0.0.1`) 返回 4
    require(`net`).isIP(`cats`) 返回 0

    prototype属性都是一个指针,实际上它只是一个地址引用着一个空间,而这个空间正是我们写的xxx.prototype.xxx

    function(){}这样的代码在运行时分配的空间。那么可见,使用原型的好处是空间只分配一次,大家都是共享的,因为它是指针。

    先看一个例子

    JavaScript

    var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(name){ alert(name); } var apple = new Computer('apple'); var dell = new Computer('dell'); Computer.prototype.isPrototypeOf(apple);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var Computer = function(name){
        this.name = name;
    }
    Computer.prototype.showMessage = function(name){
        alert(name);
    }
     
    var apple = new Computer('apple');
    var dell = new Computer('dell');
    Computer.prototype.isPrototypeOf(apple);

    在解释这个原型链之前,还要明白Js的一个特性,就是如果自身不存在,它会沿着原型往上查找。它的原理稍微有些绕,Computer自身的prototype是指向它自身的原型对象的,而每一个函数又有一个constructor指向它自身,prototype.constructor又指向它自身。于是Computer的两个实例apple,dell内部有一个proto属性是指向Computer.prototype的,最后的结果是它们可以使用showMessage方法。

    当然它们还有一个搜索原则,比如在调用showMessage的时候,引擎先问apple自身有showMessage吗?“没有”,继续搜索,apple的原型有吗,“有”,调用。所以从这里可以看出,this.showMessage是会覆盖prototype.showMessage的。

    另外还可以使用isPrototypeOf来检测一个对象是否在另一个对象的原型链上,上述的代码返回的是true。

    JavaScript

    apple.hasOwnProperty('name') apple.hasOwnProperty('showMessage')

    1
    2
    apple.hasOwnProperty('name')
    apple.hasOwnProperty('showMessage')

    使用hasOwnProperty来检测到底是对象属性还是原型属性,使用this创建的属性是一个对象属性。

    从上面可以看出来原型链的好处,但是它也不是万能的,正因为指针的存在,对于某些引用类型来说这个就非常不好了,我需要保持原对象属性值是每一个对象特有的,而不是共享的,于是把之前的构造函数与原型组合起来,也就解决了这样的问题。

    JavaScript

    var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(){ alert(this.name); } var apple = new Computer('apple'); apple.showMessage();

    1
    2
    3
    4
    5
    6
    7
    8
    var Computer = function(name){
        this.name = name;
    }
    Computer.prototype.showMessage = function(){
        alert(this.name);
    }
    var apple = new Computer('apple');
    apple.showMessage();

    这样的结果是在对象中都会创建一份属于自己的属性,而方法则是共享的。

    动态原型模式

    有时候遇到某些问题需要动态添加原型,但是实例中是不能添加的,所以绕来一下,在初始化构造函数中添加。

    JavaScript

    var Computer = function(){ if(typeof this.showMessage !== 'function'){ Computer.prototype.showMessage = function(){ } } }

    1
    2
    3
    4
    5
    6
    7
    var Computer = function(){
        if(typeof this.showMessage !== 'function'){
            Computer.prototype.showMessage = function(){
     
            }
        }
    }

    只要初始化了一次,以后就不用修改了。

    os.EOF

    不知道你有没有手写过行结束符,看上去可不漂亮啊。NodeJS内置了os.EOF,其在Windows下是rn,其他地方是n,使用os.EOL能够让你的代码在不同的操作系统上保证一致性:

    JavaScript

    const fs = require(`fs`); // bad fs.readFile(`./myFile.txt`, `utf8`, (err, data) => { data.split(`rn`).forEach(line => { // do something }); }); // good const os = require(`os`); fs.readFile(`./myFile.txt`, `utf8`, (err, data) => { data.split(os.EOL).forEach(line => { // do something }); });

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const fs = require(`fs`);
    // bad
    fs.readFile(`./myFile.txt`, `utf8`, (err, data) => {
      data.split(`rn`).forEach(line => {
        // do something
      });
    });
    // good
    const os = require(`os`);
    fs.readFile(`./myFile.txt`, `utf8`, (err, data) => {
      data.split(os.EOL).forEach(line => {
        // do something
      });
    });

    寄生构造函数模式

    这种模式的原理就是在一个函数中封装需要创建对象的代码,然后返回它。

    JavaScript

    var test = function(name){ return { 'name':name } } var g = new test('apple'); var f = de('dell');

    1
    2
    3
    4
    5
    6
    7
    var test = function(name){
        return {
            'name':name
        }
    }
    var g = new test('apple');
    var f = de('dell');

    看起来它跟工厂模式还是很像的,

    HTTP 状态码

    NodeJS帮我们内置了HTTP状态码及其描述,也就是http.STATUS_CODES,键为状态值,值为描述:
    图片 4

    你可以按照如下方法使用:

    JavaScript

    someResponse.code === 301; // true require(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // true

    1
    2
    someResponse.code === 301; // true
    require(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // true

    稳妥模式

    这种模式主要是在解决需要安全的环境中使用,一般来说一个类如果不提供getter,setter方法,是不允许直接访问和修改的。

    JavaScript

    var computer = function(name){ var _name = name; return { 'getter':function(){ return _name; }, 'setter':function(name){ _name = name; } } }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var computer = function(name){
        var _name = name;
        return {
            'getter':function(){
                return _name;
            },
            'setter':function(name){
                _name = name;
            }
        }
    }

    这样的方式可以保证属性或者说是数据的安全性,不允许直接随便修改,如果不提供setter方法的话,压根就不允许。

    避免异常崩溃

    有时候碰到如下这种导致服务端崩溃的情况还是挺无奈的:

    JavaScript

    const jsonData = getDataFromSomeApi(); // But oh no, bad data! const data = JSON.parse(jsonData); // Loud crashing noise.

    1
    2
    const jsonData = getDataFromSomeApi(); // But oh no, bad data!
    const data = JSON.parse(jsonData); // Loud crashing noise.

    我为了避免这种情况,在全局加上了一个:

    JavaScript

    process.on(`uncaughtException`, console.error);

    1
    process.on(`uncaughtException`, console.error);

    当然,这种办法绝不是最佳实践,如果是在大型项目中我还是会使用PM2,然后将所有可能崩溃的代码加入到try...catch中。

    本文由金沙国际官网发布于web前端,转载请注明出处:static以及继承,前端开发基础

    关键词: