一、前言
- 基础数据类型:number、string、boolean、null、undefined、object
- 顺序、条件、循环、比较
- 函数
- 运算符
强烈的推荐一本书:JavaScript高级程序设计(第4版) (豆瓣)【前13章之前】
正向的根基决定了逆向的上限
二、自测
JavaScript 测验https://www.w3school.com.cn/js/js_quiz.asp
三、原型链
3.1 定义一:
- 每个函数都有 prototype 和 __proto__
- 每一个对象/构造函数实例(这个也是对象)都有 __proto__
- 实例的 __proto__ 指向构造函数的 prototype。这个称为 构造函数的原型对象
- js 引擎会沿着 __proto__ -> prototype 的顺序一直往上方查找,找到window.Object.prototype 为止,Object 为原生底层对象,到这里就停止了查找,如果没有找到,就会报错或者返回 undefined
- 而构造函数的 __proto__ 指向 Function.prototype ƒ () { [native code] } 【构造器函数,但这个叫法 并不准确,它目前没有一个合适的中文名】
- __proto__是浏览器厂商实现的,W3C规范中并没有这个东西
3.2 定义二:
- JS 代码还没运行的时候,JS 环境里已经有一个 window 对象了。函数是对象【所有的默认方法都是window的属性,比如eval、function、document等,包括声明变量时。】
- window 对象有一个 Object 属性,window.Object 是一个函数对象
- window.Object 这个函数对象有一个重要属性是 prototype
- window.Object.prototype 里面有一堆属性
- 所有的实例函数__proto__都会 指向构造函数的 prototype
- constructor 是反向的 prototype
3.3 举例
四、函数进阶
首先:JS中所有事物都是对象,对象是拥有属性和方法的数据。所以函数也是对象
当创建一个函数的时候,发生了什么?
实际上,函数是Function类型的实例,此时可以把每一个创建出来的函数,当成是Function类型的实例对象,所以函数本身拥有的对象属性,来源于Function
Fn. Constructor 即为 Function
但是与此同时要注意:Function.prototype.proto === Object.prototype
可以理解为:构造器函数的构造函数是Object
也可以简单的理解:函数即对象
4.1 构造函数
- 在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般大写
- 构造函数的执行过程
(1) 当以 new 关键字调用时,会创建一个新的内存空间,标记为 Person 的实例
(2) 函数体内部的 this 指向该内存,每当创建一个实例的时候,就会创建一个新的内存空间
(3) 给 this 添加属性,就相当于给实例添加属性
(4) 由于函数体内部的this指向新创建的内存空间,默认返回 this ,就相当于默认返回了该内存空间
4.2 声明式函数
声明式会导致函数提升,function会被解释器优先编译。即我们用声明式写函数,可以在任何区域声明,不会影响我们调用
function XXX(){}
4.3 函数表达式
函数表达式我们经常使用,而函数表达式中的function则不会出现函数提升。而是JS解释器逐行解释,到了这一句才会解释。因此如果调用在函数表达式之前,则会调用失败。
var k = function(){};k() // 可以正常调用
fn1();function fn1(){}//可以正常调用
fn2();var fn2 = function(){}//无法调用
4.4 匿名函数表达式
- (function(){alert("wertyui")})()
- !function(){alert("wertyui")}()
- ~function(){alert("wertyui")}()
- -function(){alert("wertyui")}()
- +function(){alert("wertyui")}()
目的是为了防止找到函数入口。匿名函数实际上是做了一个虚拟机。
五、面向对象
5.1 封装
封装:把客观事物封装成抽象的类,隐藏属性和方法,仅对外公开接口。
在ES6之前,是不存在class这个语法糖类的。所以实现大多采用原型对象和构造函数
私有属性和方法:只能在构造函数内访问不能被外部所访问(在构造函数内使用var声明的属性)
公有属性和方法(或实例方法):对象外可以访问到对象内的属性和方法(在构造函数内使用this设置,或者设置在构造函数原型对象上比如Cat.prototype.xxx)
静态属性和方法:定义在构造函数上的方法(比如Cat.xxx),不需要实例就可以调用(例如Object.assign())
在ES6之后,存在class这个语法糖类。当你使用class的时候,它会默认调用constructor这个函数,来接收一些参数,并构造出一个新的实例对象(this)并将它返回,因此它被称为constructor构造方法(函数)。
5.2 继承
继承:继承就是子类可以使用父类的所有功能,并且对这些功能进行扩展。
比如我有个构造函数A,然后又有个构造函数B,但是B想要使用A里的一些属性和方法,一种办法就是让我们自身化身为CV侠,复制粘贴一波。还有一种就是利用继承,我让B直接继承了A里的功能,这样我就能用它了。
- 原型链继承
- 构造继承
- 组合继承
- 寄生组合继承
- 原型式继承
- 寄生继承
- 混入式继承
- class中的extends继承
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
A instanceof B
实例对象A instanceof 构造函数B
检测A的原型链(proto)上是否有B.prototype,有则返回true,否则返回false
5.3 多态
class中的多态:extends、super
多态的实际含义是:同一操作作用于不同的对象上,可以产生不同的解释和不同的执行结果。
对于js多态的详细解释:https://juejin.cn/post/6844904126011146254
六、语法
6.1 this与new
this,是指当前的本身,在非严格模式下this指向的是全局对象window,而在严格模式下会绑定到undefined。
- “use strict”----> 严格模式,减少js中不严谨的因素。
- 相同的代码在两种模式下有可能会出现不同的结果。
this的5种绑定方式:
- 默认绑定(非严格模式下this指向全局对象, 严格模式下this会绑定到undefined)
var a = 10;function foo(){console.log(this.a)};foo();window.a;
"use strict";var a=10;function foo(){console.log("this1",this);console.log(window.a);console.log(this.a)};console.log(window.foo);console.log(‘this2’,this);foo()
- 隐式绑定(当函数引用有上下文对象时, 如 obj.foo()的调用方式, foo内的this指向obj)
- 显示绑定(通过call()或者apply()方法直接指定this的绑定对象, 如foo.call(obj))
- new绑定
- 箭头函数绑定(this的指向由外层作用域决定的)
6.2 call、apply、bind
- 都是对函数的操作,使用方式:函数.call
- 都是用来改变函数的 this 对象的指向的。
- 第一个参数都是 this 要指向的对象。
- 都可以利用后续参数传参。
- call 接受函数传参方式为:fn.call(this, 1, 2, 3)
- apply 接受函数传参方式为:fn.apply(this,[1, 2, 3])
- bind 的返回值为一个新的函数,需要再次调用: fn.bind(this)(1, 2, 3)
6.3 new的过程中到底发生了什么?
- 新生成了一个对象
- 链接到原型
- 绑定 this
- 返回新对象
七、一些常见非指纹built-in函数
unescape eg:unescape('%21')
Function 函数实例化方法
eval
Array
Object
Date
RegExp
indexOf
hasOwnProperty
decodeURIComponent encodeURI encodeURIComponent
Math 、round 、 random、parseInt 等强制转换
shift、pop、push、unshift
slice、splice、split、substring、substr、concat
String 、 fromCharCode、 charCodeAt
atob 、btoa、Uint8Array、 ArrayBuffer、 Int32Array、 Int16Array
setTimeout 、setInterval、 clearTimeout
八、三元表达式
8.1 一般三元表达式
boolean_expression ? true_value : false_value;
boolean_expression: 布尔表达式,表达式在参与三元运算中必须求得一个布尔类型的值,要么是 true,要么是 false,结果作为判断依据,判断到底去:前面的值还是后面的值
true_value:布尔表达式的值为真时,三元表达式的结果
false_value:布尔表达式的值为假时,三元表达式的结果
作用:根据布尔表达式的结果,如果为真,三元表达式结果就是真值,如果为假,三元表达式结果就是假值
8.2 嵌套的三元表达式
二层嵌套 x = a > 10 ? 1 : a > 1 ? 2 : 3
三层嵌套 x = a > 10 ? 1 : a > 1 ? 2 : a > -5 ? 3:4
三层嵌套加赋值 a = a > 10 ? 1 : a > 1 ? 2 : a > -5 ? 3:4
三层嵌套加赋值加函数调用 错误情况1
function a(a){ a *= a};
a = a() > 10 ? 1 : a > 1 ? 2 : a > -5 ? 3:4
三层嵌套加赋值加函数正常调用 错误情况2
function a(a){ a *= a};
a = a(4) > 10 ? 1 : a > 1 ? 2 : a > -5 ? 3:4
三层嵌套加赋值加函数正常调用 正确情况
function a(a){ return a *= a};
a = a(4) > 10 ? 1 : a > 1 ? 2 : a > -5 ? 3:4
九、箭头函数
ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
为什么叫Arrow Function?因为它的定义用的就是一个箭头
x => x * x
相当于
function (x) {
return x * x;
}
十、运算符
10.1 位运算符
位运算符有 7 个,分为两类:逻辑位运算符:位与(&)、位或(|)、位异或(^)、非位(~)
移位运算符:左移(>)、无符号右移(>>>)
10.2 逻辑运算符
下面我们来尝试读一下 代码:
- [!+[]+!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]
- (![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]]
十一、函数内arguments
简单来说:arguments 是一个对应于传递给函数的参数的类数组对象
arguments 对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments 对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。例如,如果一个函数传递了三个参数,你可以以如下方式引用他们:
arguments.callee:指向当前执行的函数(在 严格模式 下,第5版 ECMAScript (ES5) 禁止使用)
argunments.length:指向传递给当前函数的参数数量
在严格模式下,剩余参数、默认参数和解构赋值参数的存在不会改变 arguments对象的行为,但是在非严格模式下就有所不同了
十二、一些眼瞎的写法(伪代码)
- 自执行函数嵌套执行 function(a, b){}(fn1,fn2)
- _0x319289 _$SW Oo0o00o0 a b c
- 函数名与不一致原因(1.构造函数、2. 重新赋值)
- 三元表达式嵌套
- 控制流平坦化
- 打包
- 魔改加密包
- 重构解释器
- 剩下的想到了再说 =.=。肯定还有很多,只能以后再总结进去了
注:jsfuck 表情包 AAEncode 等属于骚操作不算在其列
十三、js反爬原理
13.1 在了解js反爬原理之前,要理解爬虫的工作原理
反爬虫的两大基本要求和问题:
- 让浏览器正常访问
- 让爬虫脚本无法访问
这就要求:
- 不影响用户体验
- 能严重阻止爬虫工程师的编程难度
13.2 确定JS反爬手段的特点
- cookie加密
- 参数加密
- headers加密
- 控制流平坦化