js逆向--一点前端知识


浏览器

组件和解释器

五个组件:

  • 用户界面 : 显示操作界面
  • 浏览器引擎: 负责将用户的操作传递给对应的渲染引擎
  • 渲染引擎: 使用三个解释器去解释对应的文档中的代码。然后根据解释器的结果重新排版页面
  • 数据存储: 在本地存储一些体积较小的数据,如 Cookies、Storage 对象等
  • 网络: 它会自动加载 HTML 文档中所需要的其他资源。

三个解释器:

  • HTML解释器
  • JavaScript解释器
  • CSS解释器

HTML

JavaScript知识

  • JavaScript 使用 Unicode 字符集。JavaScript 引擎内部,所有字符都用 Unicode 表示。

类型

六种基本数据类型

  • 数值(number):整数和小数(比如1和3.14)。

    • JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数。
    • 由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
    • Infinity
      • Infinity表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity。
      • 由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript 都不报错,所以单纯的数学运算几乎没有可能抛出错误。
      • Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)。
      • Infinity与NaN比较,总是返回false。
  • 字符串(string):文本(比如Hello World)。

    • 字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。
    • 但是,字符串与数组的相似性仅此而已。实际上,无法改变字符串之中的单个字符。
  • 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)。

    • 如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true。
      • undefined
      • null
      • false
      • 0
      • NaN
        • 表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。
        • NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。
        • NaN不等于任何值,包括它本身。
        • NaN与任何数(包括它自己)的运算,得到的都是NaN。
      • ""或''(空字符串)
      • 注意,空数组([])和空对象({})对应的布尔值,都是true。
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值。

  • null:表示空值,即此处的值为空。

  • 对象(object):各种值组成的集合。

对象

  • 查看一个对象本身的所有属性,可以使用Object.keys方法。
  • 删除属性
    • delete命令用于删除对象的属性,删除成功后返回true
    • 删除一个不存在的属性,delete不报错,而且返回true
    • 只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。(定义属性不能删除,如defineProperty)
    • delete命令只能删除对象本身的属性,无法删除继承的属性
  • 判断属性是否存在
    • in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false。它的左边是一个字符串,表示属性名,右边是一个对象。
    • hasOwnProperty:方法判断一下,是否为对象自身的属性。
  • 遍历属性
    • for...in循环用来遍历一个对象的全部属性。
      • 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
      • 它不仅遍历对象自身的属性,还遍历继承的属性
    • 只想遍历对象自身的属性,应该结合使用hasOwnProperty方法

Object

  • JavaScript 的所有其他对象都继承自Object对象,即对象都是Object的实例。

  • Object对象的原生方法分成两类:Object本身的方法与Object的实例方法。

    • Object对象本身的方法:直接定义在Object对象的方法
    • Object的实例方法:所谓实例方法就是定义在Object原型对象Object.prototype上的方法。它可以被Object实例直接使用。
  • 构造函数

    • 典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。所谓“类”就是对象的模板,对象就是“类”的实例。但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
    • 构造函数就是一个普通的函数,new命令的作用,就是执行构造函数,返回一个实例对象。
          var obj= function () {
              this.price= 1000;
          };
          var v = new Vehicle();
          v.price // 1000
      
      
  • 静态方法

    • 所谓“静态方法”,是指部署在Object对象自身的方法
    • Object.keys() 遍历对象的属性。返回可枚举的属性名
    • Object.getOwnPropertyNames() 遍历对象的属性。可返回不可枚举的属性名
    • Object.getOwnPropertyDescriptors() 获取对象所有属性的描述对象。
    • Object.defineProperty():通过描述对象,定义某个属性。
    • Object.defineProperties():通过描述对象,定义多个属性。
      • 可以重写get set,来进行hook
            var obj = Object.defineProperty({}, 'p', {
                get: function () {
                    return 'getter';
                },
                set: function (value) {
                    console.log('setter: ' + value);
                }
            });
        
            obj.p // "getter"
            obj.p = 123 // "setter: 123"
        
        
      • 监听cookie
            var cookie = document.cookie;
            document = Object.defineProperty(document, 'cookie', {
                get: function () {
                    console.log('getter: ' + cookie);
                    return cookie;
                },
                set: function (value) {
                    console.log('setter: ' + value);
                    cookie = value
                }
            });
        
        
    • Object.getPrototypeOf方法返回参数对象的原型 (检测点)
    • Object.setPrototypeOf() 方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。
  • Object 的实例方法

    • Object.prototype.valueOf() 返回当前对象对应的值。
      • 对象转字符串:.valueOf | .toString 重写方法 类比java 的object toString
    • Object.prototype.toString() 返回当前对象对应的字符串形式。
      • 由于实例对象可能会自定义toString方法,覆盖掉Object.prototype.toString方法,所以为了得到类型字符串,最好直接使用Object.prototype.toString方法。通过函数的call方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。
    • Object.prototype.toLocaleString() 返回当前对象对应的本地字符串形式。
    • Object.prototype.hasOwnProperty() 判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。
    • Object.prototype.isPrototypeOf() 判断当前对象是否为另一个对象的原型。
    • Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。
  • 创建对象的方式

    • 直接赋值,var obj = {},var obj = Object({})(转为对象)
    • new关键字创建,var obj = new Object();
    • Object.create:构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法。
    • 区别:
      • 字面量和new关键字创建的对象是Object的实例,原型指向Object.prototype,继承内置对象Object
      • Object.create(arg, pro)创建的对象的原型取决于arg,arg为null,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象
  • 对象的拷贝

    • 如果要拷贝一个对象,需要做到下面两件事情。
      • 确保拷贝后的对象,与原对象具有同样的原型。
      • 确保拷贝后的对象,与原对象具有同样的实例属性。
          function copyObject(orig) {
              return Object.create(
                  Object.getPrototypeOf(orig),
                  Object.getOwnPropertyDescriptors(orig)
              );
          }
      
      
  • 原型对象object.prototype

    • JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
    • JavaScript 规定,每个函数都有一个prototype属性,指向一个对象。每个对象都有一个__proto__属性,指向原型。
    • 原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
    • 当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
  • 原型链

    • JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……
  • constructor

    • prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
          function P() {}
          P.prototype.constructor === P // true
          检测环境点:
          document.constructor === HTMLDocument
          navigator.constructor === Navigator
      
    • constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
    • 有了constructor属性,就可以从一个实例对象新建另一个实例。
          function Constr() {}
          var x = new Constr();
          var y = new x.constructor();
          y instanceof Constr // true
      
  • 包装对象

    • 对象是 JavaScript 语言最主要的数据类型,三种原始类型的值——数值、字符串、布尔值——在一定条件下,也会自动转为对象,也就是原始类型的“包装对象”
    • Boolean() , String() , Number()
    • 使用new 则为创建包装对象,不使用为强转
  • this关键字

  • this关键字是一个非常重要的语法点。毫不夸张地说,不理解它的含义,大部分开发任务都无法完成。

  • 简单说,this就是属性或方法“当前”所在的对象。

  • this主要有以下几个使用场合。

    • 全局环境使用this,它指的就是顶层对象window。
    • 构造函数中的this,指的是实例对象。
    • 如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。
  • 绑定this的方法

    • this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。
    • JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。
    • Function.prototype.call(thisValue, arg1, arg2, ...)
      • 函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。
    • Function.prototype.apply(thisValue, [arg1, arg2, ...])
      • apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。
    • Function.prototype.bind(thisValue, arg1, arg2, ...)
      • 用于将函数体内的this绑定到某个对象,然后返回一个新函数。
    • 示例:
          var d = new Date();
          d.getTime() // 1481869925657
      
          var print = d.getTime;
          print() // Uncaught TypeError: this is not a Date object.  这样在全局作用域内调用,函数里面的this指向了window对象
      
          var print = d.getTime.bind(d);
          print() // 1481869925657
      
      

函数

  • 函数的3种声明方式

    • function 命令
    • 函数表达式
    • Function 构造函数
      • 你可以传递任意数量的参数给Function构造函数,只有最后一个参数会被当做函数体,如果只有一个参数,该参数就是函数体。
      • Function构造函数可以不使用new命令,返回结果完全一样
      • 总的来说,这种声明函数的方式非常不直观,几乎无人使用(除了混淆)
  • 立即调用的函数表达式

    • 根据 JavaScript 的语法,圆括号()跟在函数名之后,表示调用该函数。
      • var f = function f(){ return 1}();
      • (function(){ /* code */ }());
      • (function(){ /* code */ })();
      • 分号都是必须的
    • 通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:
      • 一是不必为函数命名,避免了污染全局变量;
      • 二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
  • 属性和方法

    • name 属性 返回函数的名字
    • length 属性 返回函数预期传入的参数个数
    • toString() 返回一个字符串,内容是函数的源码
    • 通过断点调试可以查看到
  • 作用域

    • 作用域(scope)指的是变量存在的范围。
      • 在 ES5 的规范中,JavaScript 只有两种作用域:
      • 一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;
      • 另一种是函数作用域,变量只在函数内部存在。
      • ES6 又新增了块级作用域。
    • 函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。
    • 在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。
    • 局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
    • 函数本身的作用域就是其声明时所在的作用域,与其运行时所在的作用域无关。
    • 函数体内部声明的函数,作用域绑定函数体内部(闭包)
  • 变量名提升

    • 与全局作用域一样,函数作用域内部也会产生“变量提升”现象。
    • var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
  • 参数的省略

    • 函数参数不是必需的,JavaScript 允许省略参数。
    • 函数的length属性与实际传入的参数个数无关,只反映函数预期传入的参数个数。
  • 传参

    • 函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。
      • 这意味着,在函数体内修改参数值,不会影响到函数外部。
    • 如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。
      • 也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
  • arguments 对象

    • 由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。
    • arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。
    • 通过arguments对象的length属性,可以判断函数调用时到底带几个参数。
    • 如果要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组
      • Array.prototype.slice.call(arguments);
      • 遍历push进数组
    • arguments对象带有一个callee属性,返回它所对应的原函数。
  • 闭包

    • 是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
    • 理解闭包,首先必须理解变量作用域。前面提到,JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。
    • JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
    • 理解闭包
      • 闭包简单理解成“定义在一个函数内部的函数”。
      • 闭包最大的特点,就是它可以“记住”诞生的环境,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
      • 闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

数组

  • 数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。
  • 任何类型的数据,都可以放入数组。
  • 本质上,数组属于一种特殊的对象。typeof运算符会返回数组的类型是object。
  • Object.keys方法返回数组的所有键名
  • 属性
    • length 属性
      -length属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length设置的值。
      -清空数组的一个有效方法,就是将length属性设为0
    • 由于数组本质上是一种对象,所以可以为数组添加属性
  • 遍历
    • for...in 循环
    • for循环
    • while循环
    • forEach
  • 空位
    • 遍历时,空位都会被跳过
    • 某个位置是undefined,遍历的时候就不会被跳过
    • 空位就是数组没有这个元素,所以不会被遍历到,而undefined则表示数组有这个元素,值是undefined,所以遍历不会跳过。
  • 类似数值的对象
    • 如果一个对象的所有键名都是正整数或零,并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”
    • 典型的“类似数组的对象”是函数的arguments对象,以及大多数 DOM 元素集,还有字符串。
    • 数组的slice方法可以将“类似数组的对象”变成真正的数组。
      • var arr = Array.prototype.slice.call(arrayLike);
    • 除了转为真正的数组,“类似数组的对象”还有一个办法可以使用数组的方法,就是通过call()把数组的方法放到对象上面。
      • Array.prototype.forEach.call(arrayLike, function);
  • 常用方法
    • isArray() 方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof运算符的不足。
    • push(),pop()
      • push: 数组的末端添加一个或多个元素、并返回添加新元素后的数组长度
      • pop:方法用于删除数组的最后一个元素,并返回该元素。
    • shift(),unshift()
      • shift:用于删除数组的第一个元素,并返回该元素
      • unshift: 用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度
    • join() 以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。
    • concat() 用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变
    • reverse() 用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组。
    • slice() 提取目标数组的一部分,返回一个新数组,原数组不变。
    • splice() 删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组。
    • sort() 对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变
    • map() 将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
    • forEach() 方法与map()方法很相似,不返回值,只用来操作数据
    • filter() 用于过滤数组成员,满足条件的成员组成一个新数组返回。
    • indexOf()返回给定元素在数组中第一次出现的位置,如果没有出现则返回-1

Math对象

  • Math是 JavaScript 的原生对象,提供各种数学功能。该对象不是构造函数,不能生成实例,所有的属性和方法都必须在Math对象上调用。
  • Math.abs()
  • Math.max(),Math.min()
  • Math.floor(),Math.ceil()
  • Math.round()
  • Math.pow()
  • Math.sqrt()
  • Math.log()
  • Math.exp()
  • Math.random()
  • 三角函数方法

Date对象

  • Date对象是 JavaScript 原生的时间库。它以国际标准时间(UTC)1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)。
  • 静态方法
    • Date.now()
    • Date.parse()
    • Date.UTC()
  • 实例方法
    • Date.prototype.valueOf()
    • Date.prototype.toString()
    • Date.prototype.toUTCString()
    • Date.prototype.toISOString()
    • Date.prototype.toJSON()

Json对象

  • JSON 格式(JavaScript Object Notation 的缩写)是一种用于数据交换的文本格式,2001年由 Douglas Crockford 提出,目的是取代繁琐笨重的 XML 格式。
  • 静态方法
    • JSON.stringify()
    • JSON.parse()

console对象

  • console.log(),console.info(),console.debug(),console.warn(),console.error() 不同日志等级的打印输出
  • console.table() 用表格形式打印一些复合类型数据 如对象、数组
  • console.count() 计数输出多少次
  • console.dir(),console.dirxml() 打印一个dom的对象形式 、目录树的形式打印dom
  • console.assert() 主要用于程序运行过程中,进行条件判断,如果不满足条件,就显示一个错误,但不会中断程序执行。这样就相当于提示用户,内部状态不正确。
  • console.time(),console.timeEnd() 用于计时,可以算出一个操作所花费的准确时间
  • console.group(),console.groupEnd(),console.groupCollapsed() 这些方法用于将显示的信息分组。它只在输出大量信息时有用
  • console.trace(),console.clear() 显示当前执行的代码在堆栈中的调用路径、清空控制台输出。

类型判断