Coder Social home page Coder Social logo

my's People

Contributors

joker20000 avatar

Watchers

 avatar

my's Issues

js检测浏览器开发者工具打开状态

本篇文章基于github上disable-devtool项目做的分析。

我这里只对项目中的部分代码进行分析,能力有限,望见谅。

项目结构方面我就不进行赘述了,仅对项目src文件夹中的文件进行分析。

src中的main.js文件中,首先引入的是disableKeyAndMenu方法,是用来阻止鼠标右键和键盘打开开发者工具的。

function disableKeyAndMenu () {
    window.addEventListener('keydown', (e) => {
        e = e || window.event;
        const keyCode = e.keyCode || e.which;
        // alert(e.keyCode);
        if (keyCode === 123 || (e.shiftKey && e.ctrlKey && e.keyCode === 73)) {
            e.returnValue = false;
            e.preventDefault();
            return false;
        }
    }, false);
    if (config.disableMenu) {
        window.addEventListener('contextmenu', (e) => {
            e = e || window.event;
            e.returnValue = false;
            e.preventDefault();
            return false;
        }, false);
    }
}

这个就很明显了,监听按键事件,如果按到可以打开开发者工具的按键就阻止默认事件。同时对页面进行监听,阻止鼠标右键事件。contextmenu就是用来监听触发内容菜单的,鼠标右键或者笔记本键盘的两指点击都可以监听到。

然后引入的是initInterval方法,该方法是用来初始化事件循环,防止用户在使用中使用其他方法打开开发者工具时页面无法得知。

export function initInterval () {
    let _pause = false;
    const pause = () => {_pause = true;};
    const goon = () => {_pause = false;};
    hackAlert(pause, goon); // 防止 alert等方法触发了debug延迟计算
    onPageShowHide(goon, pause); // 防止切后台触发了debug延迟计算

    interval = window.setInterval(() => {
        if (_pause) return;
        calls.forEach(fn => {fn(time++);});
        console.clear();
    }, config.interval);
    // 两秒之后判断 如果不是pc去掉定时器interval,为了优化移动端的性能
    // 如果控制面板被打开了该定时器timer会被清除
    timer = setTimeout(() => {
        if (!isPC()) {
            clearDDInterval();
        }
    }, config.stopIntervalTime);
}

其中isPc, hackAlter, onPageShowHIde是项目中封装的代码,都比较简单,我就不细说了。
简而言之这部分代码的功能就是如果是pc端打开网页,就执行事件循环检测开发者工具的运行状态。

然后就是核心部分的代码,initDetectors.

这个方法里面调用了三种方法去判断开发者工具的打开状态。

1:toString

function detector () {
    const isQQ = isQQBrowser();
    const isFF = isFirefox();
    //因为这个方法在chrome中执行有点问题,所以如果是chrome浏览器则直接返回,不再执行。
    if (!isQQ && !isFF) return;
    let lastTime = 0;
    const reg = /./;
    console.log(reg);
    reg.toString = function () {
        if (isQQ) { // ! qq浏览器在控制台没有打开的时候也会触发 打开的时候会连续触发两次 使用这个来判断
            const time = new Date().getTime();
            if (lastTime && time - lastTime < 100) {
                triggerOnDevOpen(DETECTOR_TYPE.TO_STRING);
            } else {
                lastTime = time;
            }
        } else if (isFF) {
            triggerOnDevOpen(DETECTOR_TYPE.TO_STRING);
        }
        return '';
    };

    registInterval(() => {
        console.log(reg);
    });
}

这个方法是基于console.log的工作原理去执行的。因为console.log的方法是根据不同浏览器去制定的,所以导致了不同的浏览器有不同的执行逻辑。我就以chrome举例来说吧。

chrome的控制台中console.log()是在打印对象的时候采用了懒加载的思路,在用户点击展开对象属性的时候,才会去获取该属性。但是chrome在执行console.log的时候就会执行一次reg的toString方法,这也是为什么无论这个开发者工具打开与否,如果toString方法里面和火狐浏览器一样去判断的话都会执行后面的代码。凭借着chrome的console.log的懒加载和直接执行的toString,可以进行判断,执行两次就是打开了开发者工具,执行一次就是没打开开发者工具。

当然,不同的浏览器有不同的情况,具体情况还需要在调试中不断修改。

2:defind-id

function detector () {
    const div = document.createElement('div');
    div.__defineGetter__('id', function () {
        triggerOnDevOpen(DETECTOR_TYPE.DEFINE_ID);
    });
    Object.defineProperty(div, 'id', {
        get: function () {
            triggerOnDevOpen(DETECTOR_TYPE.DEFINE_ID);
        },
    });
    registInterval(() => {
        console.log(div);
    });
}

通过创建一个div,并对div的id进行get监听,在控制台打印这个div的时候就可以得知开发者工具的打开状态。而且由于打印的懒加载,也可以确定如果开发者工具没有打开,div的id的get方法不会执行。

3:size

function checkWindowSizeUneven () {
    const threshold = 160;
    const widthUneven = window.outerWidth - window.innerWidth > threshold;
    const heightUneven = window.outerHeight - window.innerHeight > threshold;
    if (widthUneven || heightUneven) {
        triggerOnDevOpen(DETECTOR_TYPE.SIZE);
        return false;
    }
    return true;
}

export default function detector () {
    checkWindowSizeUneven();
    window.addEventListener('resize', () => {
        setTimeout(checkWindowSizeUneven, 100);
    }, true);
}

第三种方法是最简单的,就是当页面打开了开发者工具,开发者工具就会占据页面的一部分,同时监听resize时间,当页面尺寸有变化的时候会做出反映。160的参数可以保证下载和一些其他情况下不会误判断。

但是如果开发者工具是单独的页面这部分就很难去有一个精准的判断。而且chrome浏览器把开发者工具放到页面下方,最小的时候可以达到153像素(版本 94.0.4606.71(正式版本) (x86_64)),有可能会影响结果。

这三种是用户无感知的三种方法,还有一种用户可以感知到的方法。

 !function () {
        const handler = setInterval(() => {
            const before = new Date();
            debugger;
            const after = new Date();
            const cost = after.getTime() - before.getTime();
            if (cost > 100) {
                consoleOpenCallback();
                clearInterval(handler)
            }
        }, 1000)
    }();

方法比较简单,就是比较两个date的时间差,如果debugger执行了,就是打开了开发者工具。但是这个方法也会受到影响,如果用户在sources中关闭了调试模式就无法做出正确的判断了。

Typescript简易入门指南

看到题目,大部分人可能感觉这个题有点老!都已经2021年了,还在说Typescript的入门。

说的对,但是能力一般,水平有限,也就仅限于能入个门了。

首先介绍一下typescript,typescript是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。当然,这是官方的说法,简单的说法就是这是一个js的库,和当年的Flow一样,是用来解决大型项目代码复杂性的一个工具。

这里解释一下,毕竟这篇文章主要是面对初学者的。众所周知,js是弱类型的脚本语言,所以开发的过程中函数的传参的返回的类型是可以随心所欲的。当一个项目比较大得时候,总是会出现多人协同开发的情况。这是员工A声明的函数被员工B复用,可能会出现不可预知的bug。例如入参类型不同导致的报错,或者因为员工B增加了一个判断导致return的类型不同而报错。当然,这只是一个小的方面,更复杂的情况也不是我能简单的用三言两语所总结的。

那么为了解决这个问题,早年间Facebook公司出品了一款叫Flow的静态类型检查工具,vue2.0版本就是使用的这个作为类型检测工具。虽然Flow现在的处境比较尴尬,但是当年这也是一个解决思路,而且比起typescript的学习路径更加平滑,引入也更加容易,毕竟Flow只是一个工具,而typescript算是一门新语言。但是我说出了大天也没有用,在2021年的今天,typescript已经赢麻了。所以我们就简单的说一说typescript吧,不是谦虚,真的是很简单的说说。

从官方文档里面可以看出,官方认为只需要5分钟就可以入门typescript,至于这个货是否简单我也不确定,官方给的demo永远是简单的,但是用起来就给我一种当年上学时候数学考试一样,学的时候好好的1+1=2,考试时候就变成了“请证明任何大于4的偶数都可以表示成2个奇素数的和”。说远了,我现在一点点介绍typescript的使用方法,如果嫌我说的不好可以直接去看typescript的官方文档,我也感觉官方文档说的挺好的。

说起typescript首先就得说这个数据类型,毕竟JavaScript和typescript就插在了type上了。

typescript拥有js数据类型:布尔型(boolean),数值(number),字符串(string),数组([]),null, undefined, 对象(object),括号里面就是typescript里面的名称。同时建议在typescript中声明变量时使用let去生命。

let str:string = 'test string';
let num: number = 1;
let bool:boolean = false;
let numArr:number[] = [2,2,3];
let strArr:string[] = ['a', 'b'];
let allArr:(number|string) = ['a', 2];
let n:null = null;
let u:undefined = undefined;
let obj:object = {};

里面需要特殊说明的是数组,数组的表达方式还有一种:

let arr:Array = [1,2,3];
后一种数组表达方式前面的Array是用来表示是数组类型,后面尖括号里面表示数组的数据类型,尖括号的这种表达方式被叫做泛型,后面会单独说(大概)。

当然,typescript不只有这么几种数据类型,还有一些js没有的类型,例如元祖,枚举,Any(这个是潘多拉的魔盒),void,Never。

所谓元祖,就是一个规定长度,规定每个位置的元素的数据类型的数组。

let tuple:[string, number] = ['a', 1];
当tuple这个元祖被声明了之后,它的长度和元素类型就被固定了,超过这个长度的被称为越界。从2.7版本开始就不支持越界了,在2.7版本之前越界的元素只要符合元素内声明过的类型就可以使用。当前官方文档的手册指南暂未修改,但是文档版本中2.7版本文档里面有说明。

Any是最可怕的一种类型,当你不知道这个变量是什么类型的时候就可以使用Any,但是当Any越用越会发现,使用Any一时爽,一直使用一直爽,然后typescript就变成anyscript了,看着满满的Any你就会怀疑当初自己是为什么要使用typescript了。简而言之,变量声明的时候使用Any,这个类型就可以随心所欲。

void是空,一般用在函数上

function fn():void {
    console.log('void function');
}

毕竟声明一个void的变量也没什么用,因为void这个类型能赋值的之后undefined和null,而这两个有自己的类型。

Never是永远走不到的一种类型。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

这是官方文档给的demo,看的有点混乱,我去知乎上面找到了尤雨溪大神的回答,答题意思是这个类型是用来报错的,可以理解为try catch去使用。

作者:尤雨溪
链接:https://www.zhihu.com/question/354601204/answer/888551021
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

举个具体点的例子,当你有一个 union type:

> interface Foo {
>   type: 'foo'
> }
> 
> interface Bar {
>   type: 'bar'
> }
> 
> type All = Foo | Bar

在 switch 当中判断 type,TS 是可以收窄类型的 (discriminated union):

> function handleValue(val: All) {
>   switch (val.type) {
>     case 'foo':
>       // 这里 val 被收窄为 Foo
>       break
>     case 'bar':
>       // val 在这里是 Bar
>       break
>     default:
>       // val 在这里是 never
>       const exhaustiveCheck: never = val
>       break
>   }
> }

注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显式声明为 never 的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:

type All = Foo | Bar | Baz
然而他忘记了在 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default branch 里面 val 会被收窄为 Baz,导致无法赋值给 never,产生一个编译错误。所以通过这个办法,你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。

上面的是尤雨溪大神的原文,我直接搬过来了,毕竟我的理解有误的概率还是比较高的。

最后,也是最复杂的类型,枚举。说数组时提及到的泛型我会在后面的文章(如果有)去细说。

枚举(enum)

我就不介绍的太复杂了,简而言之,枚举就是一个对象,基本上我是这么理解的。当然,绝对不可能是一样的,但是还是有一定的相似度的,初步理解的时候可以带入成有其他功能的对象。

首先我们用官网的数字枚举为例

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

如上,我们定义了一个数字枚举, Up使用初始化为 1。 其余的成员会从 1开始自动增长。 换句话说, Direction.Up的值为 1, Down为 2, Left为 3, Right为 4。

我们还可以完全不使用初始化器:

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

现在, Up的值为 0, Down的值为 1等等。 当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的。

这是数字枚举的基本用法,凭借着自增长的行为,可以减少我们声明变量的操作。当然,也会有人好奇,这个东西有什么用,那我们就要回到typescript本身的作用上来。typescript是用来做大型项目管理的,当项目过大的时候因为多人同时开发,会有因部分代码每个人理解不同而产生的bug出现,而typescript就可以减少这部分bug的出现。之前说的只是数据类型的不同导致的bug,但是存在一种情况,就是前端后端约定了固定的字段,但是后面接收的人并不知道,导致字段传错了,这个时候枚举就用上了。

假设一个接口,前端后端约定传参限制在1,2,3这三个数字里面,那么在typescript声明变量的时候就可以用枚举。

enum numToBack {
    one = 1,
    two,
    three
}
function fn(num:numToBack) {
    /*TO DO ....*/
}

这样,传入的参数就被限制在1,2,3里面了,这就是我暂时能想到的枚举的用途。

枚举还有字符串枚举,和数字枚举差不多,只不过字符串枚举需要每个变量都有一个值,毕竟字符串么米办法自增。

还有异构枚举,就是指枚举里面的变量都是不同类型的。同时枚举的值可以是需要计算的值

enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}

而且枚举的类型不一定非要是基础类型

enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square,
    //    ~~~~~~~~~~~~~~~~ Error!
    radius: 100,
}

interface是typescript的接口类型,我后面的文章(如果有)会细讲。

而且还有一个反向映射的功能,这个官方文档说的就很清楚了,我直接引用官方文档了。

除了创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了

反向映射
,从枚举值到枚举名字。 例如,在下面的例子中:

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
TypeScript可能会将这段代码编译为下面的JavaScript:

var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"

大体就是这些,能力一般,水平有限,希望评论区里面能将我文章里面的错误指出来,既避免了误人子弟,也帮助我学习进步。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.