JavaScript 简介
诞生
本名 ECMAScript, 被创建的原因是, 在浏览器输入数据时需要进行验证, 而不是传到服务器才告知客户数据错误或者空白等, 于是被创建时诞生在 NetScape 浏览器中.
特点
只能在浏览器中运行, 不能单独运行, 不能读取文件
由浏览器中的执行模块(JS引擎)执行, 考虑到页面打开的速度, 不编译执行.
功能
操作HTML的DOM
浏览器从服务器取到HTML页面之后, 会展示页面出来, 但是浏览器内部将HTML组织成一个树给JS, 这个树称为 DOM.
JS可以定位并操作DOM中的任意一个节点, 且不用刷新页面, 操作就可以立刻显示出效果. 而且操作是内部进行, 并没有改变 html的源码.
控制浏览器
打开窗口/在一个窗口内前进后退/获得浏览器名称+版本(判断是什么浏览器, 才能做这个浏览器支持的特殊操作)…
异步调用
不能像java一样访问网络, 就不能调用服务器的接口去获取数据. 用户只能get或者post向服务器发送请求, 服务器返回整个页面, 而不是一个片段, 整个页面得重新刷新.
XMLHttpRequest 使得 JS 可以直接向服务器发起接口调用, 等获得服务器返回的数据(此时为XML)后执行浏览器提供的回调函数. Called 异步调用. 回调函数基本就是更新DOM树的某个节点, 实现网页的局部刷新. 后来上述的异步调用被称为 AJAX (Asynchronous JavaScript And XML).
由于XML的标签太多, 真正数据很少, 而且需要XML解析器进行解析, 后来 JS 和服务器之间的数据传输使用 JSON 这种更简洁的格式.
HTML 结构, CSS 展示, JS(AJAX JSON) 逻辑 = 前端. 可以在浏览器实现 MVC.
后来出现了多种框架, ExtJS/prototype/JQuery/AngularJS将前端推向另一个高峰.
JS 移动到服务器端
需要满足下述两个要求:
引擎移动到服务器端, 需要执行地足够快. Chrome V8
绕开 JAVA 服务器的问题, 即线程遇到IO/数据库/网络这样的耗时操作, 不能等待, 换成异步处理.
即后来出现的 node.js, 巨大优势就是: 前后端均使用JS开发.
Node.js 工作特点
只用一个线程来处理所有请求, 事件驱动编程.
需要等待的操作, 会有一个回调函数在那, 线程不会等待. 操作一完成则发出事件通知线程, 线程立马回来执行对应的回调函数, 执行完回调函数再去接着执行那些不需要等待的操作.
即: 需要等待的操作先跳过, 先去执行那些不需要等待的操作, 耗时操作完成后事件通知线程后, 线程再立即回来执行其对应的回调函数. 事件驱动编程, 有需要处理的事件才去处理, 耗时操作先跳过.
数据类型
值类型
* 布尔值 Boolean
true/false
* 字符串 String
创建
1 | let s1 = 'hello'; // 字面量创建 |
字符串一旦创建, 其内容不可被修改, 只能重新被赋值.
1 | let str = "hello"; |
属性
length
字符串长度
1 | let s = 'hello'; |
constructor
对创建该对象的函数的引用
1 | let s = 'hello'; |
方法
处理
toUpperCase
字符串变大写
toLowerCase
字符串变小写
1 | let s = 'hello', t = 'JINLING'; |
trim
去除字符串两边的空白. 原字符串未改变.
1 | let str = " hello * "; |
split
根据分隔符将字符串分割为数组
1 | let s = 'hel,l,o'; |
查找
indexOf
查找字符串中有无指定字符串, 有则返回下标, 没有则返回-1
1 | let s = "hello jinling!" |
includes
查找字符串是否包含指定子串, 有则返回true, 反之false.
1 | let str = "hello jinling good"; |
charAt
返回字符串中对应下标的字符
1 | let s = "hello*jinling!" |
拼接/截取
concat
拼接两个或者更多字符串, 返回新字符串, 不改变原字符串.
1 | let s1 = "hello", s2 = "*go", s3 = "*hhh"; |
slice
截取字符串的片段, 不改变原字符串.
1 | let str = "helloWorld"; |
substring
截取字符串的片段, 不改变原字符串.
1 | let s = 'helloWorld'; |
substr
截取指定长度的子串. (ps. ECMAscript 没有对该方法进行标准化,因此反对使用它。)
1 | let s = 'helloWorld'; |
* 数字 Number
数字可以是数字或者对象, Number 对象是原始数值的包装对象. JS只有一种数字类型.
创建
1 | /* 基础类型创建 */ |
属性
返回对创建此对象的 Number 函数的引用.
1 | let a = 8; |
方法
toString
将数字转变为字符串, 使用指定的基数.
1 | let t = new Number("99"); |
valueOf
返回一个 Number 对象的基本数字值.
1 | let t = new Number("99"); |
isFinite
判断参数是否为无穷大
1 | Number.isFinite(123) // true |
isNaN
使用全局函数判断NaN(教程推荐).
1 | let a = NaN; |
* Symbol (ES6)
基本数据类型, ES6新增, 表示独一无二的值. 由于 ES5 对象的属性名只能是字符串, 容易造成属性名的冲突, 需要独一无二的值.
具有静态属性与静态方法. 模拟对象私有属性.
概述
通过Symbol函数产生.
1 | // 接受字符串作为参数, 表示对Synbol实例的描述, 主要为了在控制台显示或者转为字符串时容易被区分. |
每个从 Symbol 返回的symbol值都是唯一的, 尽管参数相同.
1 | let sym = Symbol(); |
Symbol可以转换为字符串以及布尔值, 但是不能转换为数值.
1 | let s1 = Symbol('happy'); |
对原始数据类型创建一个显式包装器对象从ES6开始不再被支持, 但是原有的 new Boolean/new String/new Number 由于遗留原因仍然可以被创建.
如果真的想创建一个Symbol包装器, 可以使用Object()函数.
1 | // symbol 是原始数据类型 不是对象 |
作为属性名
1 | let mySymbol = Symbol(); |
Symbol 作为对象属性时, 不能使用点运算符, 只能使用方框[].
1 | let mySymbol = Symbol(); |
* null
null : 表示主动释放指向对象的引用.
1 | let a = [1,2] |
设计之初, null 像在java里一样, 被当成一个对象.
1 | console.log(typeof null); // object |
可以自动转为 0
1 | console.log(Number(null), 8 + null); // 0 8 |
用法: null 表示”没有对象”, 即 该处不应该有值.
- 作为函数的参数, 表示该函数的参数不是对象
- 作为对象原型链的终点
1 | console.log(Object.getPrototypeOf(Object.prototype)); // null |
* undefined
Brendan Eich 觉得, 表示’无’的值, 最好不是对象. 其次, 由于js初版本没有错误处理机制, null 自动转为 0 不容易发现错误. 于是 Brendan Eich又设计了一个undefined.
一开始 undefined 被设计为表示’无’的原始值, 转为数字时为 NaN
1 | console.log(Number(undefined), 8 + undefined); // NaN NaN |
用法: undefined 表示”缺少值”, 就是此处应该有一个值, 但是还没有定义.
- 变量被声明过, 但是没有赋值, 等于 undefined
- 调用函数时, 应该提供的参数没有提供, 则该参数为 undefined
- 对象没有赋值的属性, 该属性值为 undefined
- 函数没有返回值时, 默认返回 undefined.
1 | // 用法 1 |
undefined 与 null 区别
两者使用 == 时为true, === 时为false.
1 | console.log(undefined == null, undefined === null); |
引用类型
* 数组 Array
创建
1 | let arr0 = []; // 字面 |
使用 Array.from 创建
语法:
1 | Array.from(arrayLike[, mapFunc[, thisArg]]) |
从 String 生成数组
1 | Array.from('foo'); // [ 'f', 'o', 'o' ] |
从 Set 生成数组
1 | const set = new Set([3, 4, 5, 5, 6, 7]); |
从 Map 生成数组
1 | const map = new Map([[1, 2], [2, 4], [4, 8]]); |
在 Array.from 中使用箭头函数
1 | Array.from([1, 2, 3], x => x *= 4); // [ 4, 8, 12 ] |
序列生成器(指定范围)
1 | const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1 }, (_, index) => start + index * step); |
属性
length
计算并返回数组长度
1 | let arr0 = [1,2,3,4]; // 字面 |
constructor
返回创建该对象的函数的引用, 因为js的一切变量都是对象, 是对象就有其构造函数.
1 | var test=new Array(); |
方法
改变原数组
Array.sort()
对数组元素进行排序, 默认是字符串顺序, 即会将数组元素转变为字符串, 然后比较字符串中字符的 UTF-1编码顺序来进行排序.
1 | // 按照字母顺序排序 默认 |
添加比值函数, 使得能对数字进行排序.
1 | // 不使用比值函数 |
Array.pop()
删除数组的最后一个元素并返回该元素. 空数组返回
undefined.
1 | let arr0 = [1, 2, 3, 4]; |
Array.shift()
删除并返回数组的第一个元素
1 | let arr1 = [1,2,3]; |
Array.unshift()
向数组的开头添加元素并返回现有长度
1 | let arr1 = [1, 2, 3, 4, 5]; |
Array.push()
向数组末尾添加元素并返回数组现有长度
1 | let arr1 = [1,2,3]; |
Array.reverse()
颠倒数组中元素顺序
1 | let arr1 = [1,2,3]; |
Array.splice()
推荐使用该方法删除数组元素
注意: 删除的元素以数组形式返回.
1 | let arr = [1, 2, 3, 4, 5] |
delete
JS运算符
1 | let arr = [1, 2, 'ok', 'fine', 'you', 'bye'] |
不改变原数组
Array.keys()
返回一个含有数组下标的 Array Iterator 对象
1 | let a = ['a', 'b', 'c']; |
由此启发可以构造一个比如包含 1-10 的数组.
1 | let a = [...Array(10).keys()].map(val => val = val + 1); |
Array.slice()
根据下标获取数组的一部分, 返回新数组.
1 | let arr1 = [1, 2, 3, 4, 5]; |
Array.concat()
拼接数组成一个新数组
1 | let arr0 = [1, 2, 3, 4]; |
Array.flat()
按照指定的深度递归遍历数组, 将所有元素与遍历到的子数组中的元素合并为一个新数组返回.
将数组扁平化
1 | /* 默认递归深度为1 */ |
Array.join() 默认使用 , 为分隔符
toString所有 JavaScript 对象都拥有toString()方法数组所有元素组成字符串, 可以指定分隔符.
1 | let arr0 = [1, 2, 3, 4]; |
Array.map()
对数组的每个元素均执行函数, 对其做一些处理, 来生成新数组. 不改变原数组.
1 | let arr = [1, 2, 'ok', 'fine', 'you', 'bye'] |
Array.filter()
对数组的每个元素均执行函数, 筛选符合条件的元素来生成新数组.不改变原数组.
1 | let arr = [1, 2, 'ok', 'fine', 'you', 'bye'] |
Array.forEach()
对数组的每个元素均执行一次函数(回调函数)
1 | let arr = [1, 2, 'ok', 'fine', 'you', 'bye'] |
Array.reduce()
参数
total默认是数组的第一个元素, 可以设置初始值.
1 | let arr = ['bye', 'hi', 'ok', 'fine', 'you', 'bye'] |
Array.reduceRight()
类似于
Array.reduce(), 只不过是从右往左遍历元素.
1 | let arr = ['bye1', 'hi', 'ok', 'fine', 'you', 'bye2'] |
Array.every()
检查数组中的元素是否都符合条件, 都符合才返回true, 否则返回false.
1 | // 有元素不符合条件 false |
Array.some()
检查是否有元素符合条件, 有则返回true, 没有则返回false.
1 | // 有元素符合条件 true |
Array.indexOf()
找到给定元素在数组中第一次出现的位置, 没有则返回-1, 找到则返回元素下标.
1 | // 找不到 -1, 找到就下标 |
Array.lastIndexOf()
与上一个类似, 只是从数组末尾开始检索.
1 | // 出现两次Bob, 但是返回了从右往左的第一个. |
Array.find()
返回符合条件的第一个元素
1 | let arr = ['hi', 'Bob', 'good', 'are', 'you', 'Bob', 'google']; |
Array.findIndex()
返回符合条件的第一个元素下标
1 | let arr = ['hi', 'Bob', 'good', 'are', 'you', 'Bob', 'google']; |
数组操作
去重
利用键本身的不可重复性
利用ES6 Set 去重 (ES6中最常用)
1 | let arr = [1, 2, 2, 3, 3, 3]; |
双层循环法
splice去重(ES5 常用)
1 | function unique (arr) { |
使用 fliter+indexOf
1 | function unique (arr) { |
伪数组
对象冒充数组, 有数组的形态其实就是有 length 的概念, 但是并不能真正使用数组的方法.
* Map (ES6)
解决js对象的键只能是字符串的问题, ES6标准新增的数据类型.
创建/添加/删除/是否包含
1 | // 二维数组 |
* Set (ES6)
一组不重复key的集合.ES6标准新增的数据类型.
创建/添加/删除/是否包含
1 | // 数组作为输入 |
* 函数
函数定义是一个常规的绑定, 其中绑定的值是函数.
函数的第一种表示法.
1 | // 大括号必要, 末尾建议带分号 |
函数也是值的一种, 可以被赋值给多个变量/作为参数传递给函数等.
1 | let func1 = () => { |
没有 return 语句或 return 后面没有返回值, 函数将返回 undefined.
1 | let func1 = () => { |
每个局部作用域可以查看包含它的局部作用域, 所有局部作用域都能看见全局作用域.
声明表示法
函数的第二种表示法.
声明在调用之后也能够工作, 声明在概念上被移到了作用域的顶部.
1 | function square (x) { |
箭头函数
函数的第三种表示法. 以较简明的方式编写小型函数表达式.
1 | // 两种写法相同 |
没有 this
访问 this, 会从外部获取
1 | let person = { |
没有 arguments
1 | function defer (f, ms) { |
使用普通函数
1 | function defer (f, ms) { |
但是这样也能正常输出, ????
1 | function defer (f, ms) { |
不能使用 new
因为没有 this, 就不能作为构造函数, 即不能使用 new.
没有 super
需要知道箭头函数是如何获取 this 值的?
调用栈
函数返回时必须跳回到调用它的位置, 所以计算机必须记住调用发生的上下文. 存储此上下文的位置是调用栈, 每次调用函数时, 当前上下文都存储在此栈的顶部.
可选参数
多余参数自动忽略, 不足参数为 undefined.
1 | let square1 = (x, y) => { |
参数设定默认值
1 | let square1 = (x, y = 3) => { |
作用域链
定义了一个函数激活执行的时候, 去哪里找变量的值.
1 | function createFunc () { |
eat 函数的作用域链如下:
1 | eat函数作用域[parent作用域-A] |
eat 函数中没有定义 desc 这个变量值, 就沿着作用域链去找, 在 createFunc 作用域中找到了 desc 变量的值, 于是就使用了.如果还没有找到, 就接着往上找.
当执行 createFunc 的时候, eat 函数被创建, 此时 eat 函数会把外部函数的作用域链记录下来, 留到执行时使用.
注意: 作用域链是函数创建时刻发生关联的, 不是运行时刻. Called 静态作用域/词法作用域. 函数被创建即函数被定义.
1 | var x = 1; |
静态作用域是实现闭包的必需条件.
执行上下文
函数在调用时在执行栈中产生的变量对象, 该对象不能直接访问, 但是可以访问其中的变量/this 对象等.
作用域是在函数声明时就确定的变量访问的规则, 执行上下文是函数执行时才产生的变量的环境, 执行上下文基于作用域进行变量的访问/函数引用等操作.
1 | let fn, bar; // 1、进入全局上下文环境 |
函数调用栈: 栈底永远是全局上下文, 栈顶是当前正在执行的上下文(活动对象), 白色是被挂起的变量对象(执行上下文)
闭包
闭包在 JS 中就是一个以函数和以静态方式存储的父作用域的一个集合体.
能够读取函数局部变量的函数就是闭包. 下面例子中, func2函数就是闭包.
1 | var func1 = () => { |
用途: 读取函数内部变量 / 让这些变量的值始终保持在内存中.
1 | let nAdd; |
证明了func1的局部变量a一直在内存中, 并没有在func1被调用后被自动清除.
因为func1是func2的父函数, 而func2被赋予了局部变量func, 导致func2一直在内存中, 则func2依赖的func1也一直在内存中, 不会在调用结束后, 被垃圾回收机制回收.
这里nAdd也是一个匿名函数, 也是一个闭包, 相当于一个setter, 可以在函数外部对函数内部局部变量进行操作.
使用闭包的注意点:
- 闭包会使函数中的局部变量在内存中, 因此会使得内存占用过多, 不能滥用. 在退出函数前, 将不使用的局部变量全部删除.
- 闭包会在函数外部, 改变父函数内部变量的值, 注意不要随便改变.
思考题:
this在函数中而不是方法中使用时, 指向全局对象
1 | var name = "The Window"; |
- 这里
that指向整个 object.
1 | var name = "The Window"; |
arguments
对应于传递给函数的参数的类数组对象, 是所有非箭头函数中可用的局部变量, 可以使用它来引用函数的参数.
- arguments 参数可以被设置
- 不是一个 Array, 只是类似, 类型是 object
- 只有 length 和索引元素功能
- 可以被转换为真正的数组
1 | function unique (a, b, c, d, e) { |
eval 函数
计算某个原始字符串(不是String对象), 并执行其中的JS代码, 并返回结果(如果不存在, 则返回undefined). 是全局对象的一个函数属性.
1 | let x = 8; |
操作符 typeof
判断变量数据类型
1 | console.log( |
let const var
const 常量, 变量名与内存地址之间建立了不可变的绑定关系.
1 | let obj = { 0: 1 }; |
内置对象 Function
Function.prototype.call()/apply()/bind()
这里使用node与浏览器的运行结果不同(原因待深究), 下面为在浏览器中的运行结果.
1 | var name = "Mary", age = 18; |
this 指上一层, 这里指向 window.
1 | var name = 'Bob'; |
用法: call apply bind 都是用来改变 this.
在调用 obj.speak 函数时,函数中含有 this, call/apply/bind 都是用来指定函数中的 this 指谁的, 这里传参是 newObj, 则指的是 newObj 这个对象.
1 | var name = "Mary", age = 18; |
三个函数在传参时候的区别
call: 参数间以逗号分割
apply: 参数全部放入一个数组中
bind: 除了返回是函数, 传参与 call 一致
1 | var name = "Mary", age = 18; |
对象 Object
使用 {} 表示, 键必须是字符串或者 Symbol 类型. 不是字符串的话会转换成字符串, 对象的话默认调用 toString 方法.
下面的例子中, 对象转换为字符串后, 键值都是 [object Object] , 因此这里值可以被更改.
1 | var a = {}, b = { key: '123' }, c = { key: '456' }; |
a.b.c.d 比a['b']['c']['d']性能更高.
因为前者只用考虑字符串的情况, 后者还需要考虑括号中是变量的情况. 从结果来看, 编译器解析前者更容易些, 因此前者更快.
ES6
对象的简洁写法, 属性名是变量名, 属性值是变量的值.
1 | function hello (x, y) { |
封装
面向对象编程的核心思想是将程序划分为更小的部分, 并使每个部分负责管理自己的状态.
接口与实现分离, 常称为封装. 常见在属性开头加上 _ 表示是私有属性.
创建对象
创建一个对象, 定义属性和方法, 不需要 Class.
对象中的方法就是保存函数的属性.
1 | let animal = { |
显式修改 方法的调用对象. 使用函数的 call 方法, 该方法将 this 值作为第一个参数, 其他参数为普通参数. 则此时 obj 是 eat 方法的调用者, 通过 call 进行了显式的调用对象的修改.
1 | let animal = { |
原型
对象有自己的默认属性集. Object.getPrototypeOf 方法返回一个对象的原型.
Object.prototype 提供在所有对象中显示的方法, 是最根部的原型.
函数派生自 Function.prototype, 数组派生自 Array.prototype, 他们具有不同的默认属性集.
1 | let obj = {} |
Object.create
使用 Object.create 创建具有特定原型的对象.
1 | let protoDog = { |
__proto__ 属性
继承是让两个对象产生关联, 使用 __proto__, 这个属性每个对象都有.
1 | let animal = { |
如下所示, 对象 dog 和 cat 的原型均是 animal, 但是均没有定义 eat 方法. 在执行 eat 方法时, 会到其原型中去寻找, 如果找到则执行, 没有则继续去原型的原型中去寻找, 直至找到或者为null. 不断寻找原型的过程依赖于__proto__建立的原型链.
可以看出, 尽管执行的是原型中的方法, 但是方法中的this仍然指的是调用该方法的上级对象, 由于是dog和cat这两个对象进行调用的, 所以 this 指向的就是这两个对象而不是 animal.
构造函数
但是 JS 也可以通过 new 关键字来创建对象, 是给不理解原型链又需要创建对象的程序员使用的.
1 | // 模仿 java 中的 Class 而提供的构造函数 |
这样有个问题就是, 每个对象都会有一个 sayHello 函数, 太重复, 而 java 中函数是定义在 class 中的.
prototype
JS 使用更加高效的方式, 创建一个原型对象 A, 将方法都放在这个原型对象 A 中, 而通过同一个构造函数创建的对象的原型, 都是这个原型对象 A, 这样对象找不到方法时, 就会去其原型即 A 中寻找.
达到这样的效果, 则需要将构造函数与原型对象 A 关联起来, 将 A 赋值给构造函数的 prototype 属性, 则 A 就会成为这个构造函数创建的对象的原型.
1 | function Student (name) { |
语法糖 Class
上述语法有点复杂, JS 推出语法糖, 将构造函数与原型对象的函数写在一个 class 中.
上述写法等同于下面这种.
1 | class Student { |
类 Class
一种语法糖, 特殊的函数, 由类表达式和类声明组成. 类定义了一种对象的形状, 具有哪些属性与方法. 而这种对象称之为类的实例.
JS 类就是带有 prototype 属性的构造函数. 类中的方法, 都是构造函数原型对象中的方法. 类中的constructor方法是实际的构造函数, 并被绑定名称 Animal.
1 | class Animal { |
定义类
类声明 带有
class关键字1
2
3
4
5
6
7// 类需要先声明 再使用 不像函数声明会提升
class hello {
constructor(height, width) {
this.height = height;
this.width = width;
}
}类表达式 可以具名或者匿名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 匿名类
let hello = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
console.log(hello.name); // hello
// 具名类
let hello = class hello2{
constructor(height, width) {
this.height = height;
this.width = width;
}
}
console.log(hello.name); // hello2传统的基于函数的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function Animal(name){
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + ' makes a noise.');
}
class Dog extends Animal{
speak(){
super.speak();
console.log(this.name + ' barks.');
}
}
let d = new Dog('cookie')
d.speak();
// cookie makes a noise.
// cookie barks.
类体和方法定义
constructor
构造函数, 一种特殊方法, 创建和初始化一个由 class 创建的对象.
构造函数可以使用super调用父类的构造函数.
覆盖派生属性
向对象中添加属性, 属性被添加到对象本身, 原型中的此属性将不再影响该对象.
1 | class Animal { |
数组原型提供的 toString 方法与基本原型对象提供的有所差别, 这样对原型属性的覆盖有利于更通用的对象类中表达异常属性.
1 | console.log(Array.prototype.toString == Object.prototype.toString); // false |
多态
对原型方法的覆盖, 以实现实例的特殊化需求. String 实际调用的仍然是 toString 方法.
1 | let dog = new Animal('dog'); |
任何支持 toString 方法的对象都可以使用它.
多态: 多态代码可以支持不同类型的值, 只要这些值支持它指定的接口. 比如 toString 方法, 所有值都支持该接口, 则所有值都能使用该方法.
映射
普通对象派生自 Object.prototype , 含有祖先原型的所有属性, 在一些实际场景下, 这些属性可能显得多余.
→ 可以创建没有原型的对象.
1 | // 传递 null 生成的对象不会从 Object.prototype 派生 |
而且普通对象要求键值必须为字符串.
→ 使用 Map 类, 存储映射并可以使用任何类型的 key.
1 | let ages = new Map(); |
set get has 是 Map 对象接口的一部分.
某种情况下, 如果确实需要使用普通对象来作为映射, 则 Object.keys 只返回一个对象自己的键, 而不包括其原型中的那些属性.
1 | class Animal{ |
hasOwnProperty 方法也只判断某键是不是该对象自己的, 没找到也不会去搜索其原型对象. 与关键字 in 不同.
1 | console.log(dog.hasOwnProperty('name'), dog.hasOwnProperty('speak')); // true false |
继承
继承允许我们构造与已有数据类型相似的数据类型.
extends 创建子类
子类的构造函数必须先使用 super 调用超类的构造函数, 因为子类需要具有超类的属性. 子类对象是其所有超类的实例.
extends 关键字表示该类不是基于 Object 原型, 而是基于其他类.
基础类称为超类, 派生类是子类.
1 | class Dog1 extends Animal { |
instanceof 运算符
判断一个对象是否来自某一个类.
1 | class Animal { |
静态方法
部署在object对象自身的方法
Object.values 获取对象的所有 value, 输出类型为数组
Object.keys 获取对象的所有 key, 输出类型为数组
1 | let obj = {key1:'val1', key2:'val2', key3:'val3'} |
Object.getOwnPropertyNames 也是返回对象的所有属性名, 但是还会返回不可枚举的属性; 可枚举属性方面, 与Object.keys相同
1 | // 不可枚举属性 二者不同 |
实例方法
Object.hasOwnProperty判断对象是否拥有某项属性
1 | let obj = {key1:'val1', key2:'val2', key3:'val3'} |
JSON
需求: 将内存中的数据保存在文件中或者通过网络发送到另一台服务器, 则必须以某种方式将内存中存储数据的地址转换成可以存储的格式.
这种转换称为序列化数据, JSON (JavaScript Object Notation) 就是一种流行的序列化格式(JSON 编码的字符串), Web 上的数据存储与通信方式.
- 属性使用双引号括起来
- 没有注释
- 只有简单的数据表达式
- JSON.parse 序列化格式->数据
- JSON.stringify 数据->序列化格式
函数式编程
摩尔定律失效, 多核时代来临, 函数式编程能够很好地为并发编程服务, 具有 没有 side effect/ 不共享变量/安全调度到任何一个CPU core 上运行/没有加锁问题…等诸多优点.
纯函数
- 对于相同的输入, 永远有相同的输出. 没有可观察的副作用, 不依赖外部条件.
- 不能修改传递给函数的参数
- 不能修改全局变量
比如数组操作中, 对于给定的数组, slice就是纯的, splice就是不纯的.
纯函数可以有效降低系统复杂性, 还有很多其他的优秀特性, 例如可缓存性.
1 | import _ from 'lodash'; |
使用递归而非迭代
使用尾递归, 保证不溢出.
1 | // 迭代不被允许 |
高阶函数
很多函数大体相同, 重复代码很多, 只有一些细节不一样, 于是产生高阶函数.
高阶函数: 让函数来产生函数, 共用的部分抽取出来, 不共用的部分与共用的部分能组合起来.
比如 JS 中的 map/filter/forEach/... 函数都是高阶函数, 能快速操作集合数据.
函数的柯里化
curry: 传递给函数一部分的参数来调用它, 让他返回一个函数去处理剩下的参数.
就是传递一部分的参数, 形成固定模式的函数(部分参数数值已经固定), 得到已经记住参数的新函数. 这样对应固定的输入, 就得到固定的输出.
1 | var check = x => (y => y > x); |
函数组合
包菜式代码 h(g(f(x))) => 更优雅 函数组合
1 | // 传的是g函数需要的参数 |
惰性求值
待补充
宏(macro)
待补充
Point Free
减少对不必要的中间变量的命名
声明式与命令式代码
命令式: 写出一条一条指令让计算机执行, 一般会涉及到很多繁琐的细节. 既说做什么, 也说怎么做.
声明式: 写表达式表明自己想做的事情, 而不是一步一步的指示. 隐藏细节. 只说做什么, 不说怎么做.
1 | //命令式 |
函数式编程一个优点就是声明式代码以及纯函数. 工作时专注于业务代码, 优化时专注于函数内部. 还有其他的特点, 比如高阶函数/函数没有side effect/只有值没有变量/用递归而不是用迭代等.
遍历器与 for…of
遍历器概念
是用来处理可遍历数据结构的统一接口, 只要部署 iterator 接口, 就可以进行遍历操作.
作用: 提供统一的访问接口/数据结构的成员按照某种顺序排列/为ES6新增的for...of服务
数据结构有遍历器接口, 就称为该数据结构是可遍历的/可迭代的.
JS 默认遍历器接口
JS中默认的遍历器接口, 即数据结构的原型对象有 Symbol.iterator 属性, 该属性对应的函数返回一个遍历器对象, 调用对象的next方法, 即可返回数据结构的下一个数据..
1 | let arr = [3, 4, 5]; |
原生具备 Iterator 接口的数据结构如下。
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
上述数据结构不用自己写遍历器函数, for...of循环会自动进行遍历.
1 | let arr = [3, 4, 5]; |
没有遍历器函数的数据结构, 可以根据实际需求进行手动部署, 即在Symbol.iterator属性上手写遍历器对象生成函数.
解构赋值
针对数组或者对象进行模式匹配, 然后对其中的变量进行赋值. 解构目标 = 解构源.
数组的解构
1 | // 基本 |
数组解构中, 若解构目标为可遍历对象(实现iterator接口的数据), 都可以进行解构赋值.
1 | // 字符串 |
解构默认值
解构匹配到undefined, 触发默认值作为返回结果.
1 | // 均匹配到 undefined |
对象的解构
1 | // 报错 |
关键字 this
js中this随着执行环境的变化而变化, 是函数运行时, 在函数体内部自动生成的一个对象, 只能在函数体内部使用. 即, this 是函数运行时所在的环境对象. 注意: 箭头函数不绑定自己的 this .
单独使用
无论有无严格模式, this始终指向全局对象. 浏览器中, 全局对象为[object Window]
1 | // 'use strict' |
纯粹的函数调用
函数中, 默认this指向全局对象
1 | var name = "ok"; |
严格模式下不允许默认绑定, 所以函数中的this为undefined
1 | // 严格模式 |
函数作为对象方法
指向上级对象
1 | let obj = { |
类方法中的 this 指向
speak 方法中的 this 从打印结果来看, 指的是构造函数生成的新对象, 并未打印speak方法.
但是 this.speak 却能够调用, 说明this不仅指向的是构造函数对应的对象, 而且在行为上也与构造函数的对象一致, 就是对象中找不到方法, 就去对象的原型中去找.
1 | class Animal { |
函数作为构造函数
构造函数就是, 通过这个函数, 能够生成一个新对象. 此时, this 指向这个新对象.
1 | function test() { |
apply 调用
apply()是函数的一个方法, 作用是改变函数的调用对象. 第一个参数表示改变后的调用这个函数的对象, 此时 this 指向这个参数.
1 | var x = "ok"; |
异步编程
ES5 callback
ES5 Promise
含义
Promise 对象包含一个异步操作, 并且保存这个异步操作的状态以及操作执行完成之后的结果.
操作执行的状态不受影响, 操作结束后状态不会改变. 后面我们默认 resolved 指 fulfilled, 不包括 rejected.
| 三种状态 | 含义 |
|---|---|
| pending | 执行中 |
| fulfilled | 已成功 |
| rejected | 已失败 |
| 备注: resolved | 表示已定型(pending->fulfilled 或者 pending->rejected), 常指 fulfilled. |
用法
Promise是一个构造函数, 用来生成Promise对象实例.
1 | const promise = new Promise(function(resolve, reject) { |
可见 Promise 构造函数只需要传一个参数, 就是一个函数, 而这个函数是有两个参数 resolve/reject, 这两个参数均是函数, 由 JS 引擎提供, 不用自己部署.
resolve/reject
resolve 函数: 在异步操作成功时调用, 将异步操作的状态变为成功(pending->resolved), 并且将异步操作的返回结果作为参数传递出去.
reject 函数: 在异步操作失败时调用, 将异步操作的状态变为失败(pending->rejected), 并且将异步操作报的错, 作为参数传递出去.
Promise实例生成以后, 可以用then方法分别指定resolved状态和rejected状态的回调函数.
1 | promise.then(function(value) { |
可见then方法有两个参数, 均是函数, 第一个回调函数在Promise对象状态变为resolved时调用, 第二个回调函数在Promise对象状态变为rejected时调用. 第二个函数是可选的, 这两个函数都接受Promise传出的值作为参数.
其中在处理失败情况时, then的第二个回调函数与catch方法只能选择一个, 如果同时写上, 则只会执行前者.
示例:
1 | let p = new Promise((resolve, reject) => { |
如上所示, 首先建立一个Promise对象, 将setTimeout这个异步操作放在里面, Promise提供了这个异步操作成功或者失败的解决方法. 异步操作执行成功了, 就调用resolve函数抛出结果, 失败就调用reject函数抛出结果, 总之就是会抛出异步操作的运行结果.
在异步操作结束以后, 会触发对象p对执行对象的处理, 即会触发调用对象p的then方法. 处理成功情况, 传参是正常的值或者是Promise实例, 实现then第一个参数即成功回调函数, 处理失败情况, 传参是Error, 实现then第二个参数即失败回调函数.
then方法执行顺序
Promise 对象新建后, 是立即执行的, 首先输出 hi.
then方法对应的回调函数会在当前脚本所有同步任务执行完才会执行, 所以yes最后输出.
1 | let p = new Promise((resolve, reject) => { |
resolve/reject 不阻止 Promise 执行
执行完resolve或者reject之后, Promise的参数函数仍然会继续执行, 所以最好在 resolve 或者 reject 前面加上 return, 防止不必要的代码执行.
1 | let p = new Promise((resolve, reject) => { |
抛出值是 Promise 实例
1 | const p1 = new Promise((resolve, rject)=>{ |
当resolve的值是另一个Promise实例时, p2的状态取决于p1的状态, 而且 p2 调用then方法, 实际上是p1在调用then方法.
1 | const p1 = new Promise((resolve, reject)=>{ |
方法
Promise.prototype.then()
then方法调用后仍然返回一个promise对象(不是原来的), 但是由于then中不能调用resolve方法, 不能抛出值, 因此then方法返回的 Promise 实例执行结束后, 是没有状态值的.
1 | let p = new Promise((resolve, reject) => { |
但是then方法中, 使用 return 进行值的返回, 可以达到和 resolve 一样的效果.
1 | let p = new Promise((resolve, reject) => { |
但是在一开始初始化变量时, 是不能使用return的, 需要使用 resolve/reject.
1 | let p = new Promise((resolve, reject) => { |
这样就可以采用链式写法, 按照一定次序执行一组回调函数.
Promise.prototype.catch()
是.then(null, rejection)的别名, 用于指定发生错误时的回调函数, 返回值是一个Promise对象, 后面还能再使用 then 方法.
1 | // 失败情况 |
catch 不仅会捕捉p发生的错误, 也会捕捉p.then中的回调函数发生的错误.
Promise 对象的错误具有”冒泡”性质, 直至被捕获为止. 因此建议使用catch方法, 而不是then的第二个参数.
1 | getJSON('/post/1.json').then(function(post) { |
如果不使用catch方法处理错误, 则 Promise 内部的错误不会影响程序的执行.
1 | let p = new Promise((resolve, reject) => { |
Promise.prototype.finally()
finally方法用于指定不管Promise对象最后状态如何, 都会执行的操作. ES2018. finally的回调函数不接受入参.
1 | promise |
在执行完then或者catch方法后, 都会执行finally方法.
ES6 Generator 函数
这个函数生成一个遍历器对象, 代表函数内部的指针, 调用对象的next方法可以对函数内部的状态值进行遍历.
调用 next 方法, 会返回一个有 value 和 done 两个属性的对象, 其中 value 表示此时指向的状态, 状态值是 yield 表达式后面那个表达式的值, done 表示有没有遍历结束.
yield 表达式表示遍历暂停的点.
1 | function* g () { |
Generator 函数返回的遍历器对象, 只有调用 next 方法才会遍历下一个状态. 若函数没有 return 语句, 则执行直至函数结束, 返回值为 undefined.
注意: yield 后面的表达式, 只有调用 next 方法, 内部指针指到该语句时, 才会执行.
1 | function* g () { |
yield 与 return 语句的区别
相似: 都能返回紧跟在语句后面的表达式的值
不同: 函数执行遇到 yield 暂定执行, 遇到 return 则中断执行. Generator 函数可以返回多个值, 因为可以有多个 yield, 但是只有一个 return.
yield 语句使用
Generator 函数不用 yield 时, 是一个暂缓执行的函数, 调用时生成遍历器对象, 对象调用 next 方法时, 执行完毕, 不调用则不执行. yield 语句只能用在 Generator 函数中.
yield 表达式在另一个表达式之中时, 需要用 () 括起来.
1 | const log = console.log; |
用作函数参数或者赋值表达式的右边, 不需要括号.
1 | function* g () { |
异步应用
Generator 函数内部可以捕获遍历器对象使用throw方法抛出的错误. 出错的代码与处理错误的代码, 实现了时间与空间上的分离.
1 | const log = console.log; |
封装异步任务
1 | import fetch from 'node-fetch'; |
ES8 await async
Generator 函数的语法糖.
Generator 函数,依次读取两个文件.
1 | import fs from 'fs' |
async 函数对 Generator 函数的改进, 有以下几点:
内置执行器.
Generator函数的执行依赖于执行器, 需要调用next方法或者co模块才能逐步执行, 得到结果.async函数就像普通函数, 只需要一行asyncFunc();, 就会自动执行, 输出最后结果语义更清楚.
async表示函数中有异步操作,await表示后面的表达式需要等待结果.适用性更广.
co模块约定,yield命令后面只能跟Promise对象或者Thunk函数,await后面可以跟Promise对象以及原始类型的值(这时等同于同步操作).返回值是
Promise对象.async函数返回值是Promise对象, 比起Generator函数返回的遍历器对象, 更加方便.
async 函数可以看成多个异步操作, 包装成的一个 Promise 对象, await 命令可以看成内部 then 命令的语法糖.
1 | const asyncFunc = async function () { |
在 Promise 实例前面加上 await, 就相当于等待这个 Promise 实例中的异步操作执行结束.
1 | const asyncFunc = async function () { |
异步操作不加 await , 就不会同步.
1 | const asyncFunc = async function () { |
Event Loop
JS 是单线程
由于它是浏览器脚本语言, 主要是与用户互动以及操作DOM, 否则同步问题很复杂. 即使 js 可以创建多线程, 但是子线程完全受控于主线程, 且不得操作 DOM, 未改变单线程的本质.
任务队列
同步任务/异步任务
异步执行的运行机制
1 | 1. 所有同步任务在主线程上执行, 形成执行栈 |
注意:
- 主线程为空, 才会读取任务(消息/事件)队列.
- 执行异步任务就是执行其指定的回调函数, 异步任务必须指定回调函数
- 任务队列是先进先出的数据结构.
- 定时器功能: 主线程首先会检查一下时间, 某些事件只有到规定时间, 才能返回主线程.
Event Loop
主线程从任务队列中读取事件是循环不断的, 整个运行机制又被称为 Event Loop.
主线程运行, 产生堆栈, 栈中的代码调用各种 API, 调用之后可能在”任务队列”中添加各种事件(click load done).
只要栈中的代码执行完毕, 就会读取”任务队列”, 依次执行事件对应的回调函数.
执行栈中的代码(同步任务), 总是在读取”任务队列”之前.
定时器
任务队列中还可以放置定时事件, 指定某些代码在多少时间后执行.
Proxy
定义
在对象外面设置一层拦截, 外界对该对象的访问必须先经过这层拦截, 因此提供一种机制, 可以对外界的访问进行过滤和改写.
1 | // 对空对象设置拦截, 重定义对属性的get与set方法 |
由代码得出, Proxy 实际上重载(overload)了点运算符, 即用自己的定义覆盖了语言的原始定义.
ES6 语法, 提供 Proxy 构造函数, 用来生成 Proxy 实例.
1 | // target 拦截对象 handler 定制拦截行为 |
用法
普通使用
构造函数两个都是对象, 第一个是目标对象, 第二个是配置对象, 对于拦截的操作需要提供对应的处理函数, 该函数则会拦截该操作.
1 | var obj = new Proxy({}, { |
注意: 如果要使 Proxy 起作用, 必须操作 Proxy 实例, 而不是操作目标对象. 如果配置对象为{}, 则对 Proxy 实例的操作相当于对target进行操作.
技巧: 可以将 Proxy实例作为object的一个属性进行调用.
1 | var object = { proxy: new Proxy(target, handler) }; |
Proxy 实例作为原型对象
obj 本身没有属性, 根据原型链去其原型对象即 proxy 对象上寻找并读取属性, 导致被拦截.
1 | var proxy = new Proxy({}, { |
一共有十三种拦截操作.
Object
Object.create()
创建一个新对象, 使用现有对象作为新对象的__proto__.
1 | const person = { |
语法
1 | Object.create(proto,[propertiesObject]) |
proto: 新创建对象的原型对象
propertiesObject: 可选, 需要传一个对象, 其自有可枚举属性将被添加到新创建对象中.
使用 propertiesObject 属性.
1 | var o; |
使用构造函数创建的空对象, 是有原型的.
1 | let obj; |
省略的属性特性默认为 false, 下述 p 属性是不可写/不可枚举/不可配置的.
1 | let obj = Object.create({}, { |
创建一个可写的/可枚举的/可配置的属性 p.
1 | let obj = Object.create({}, { |
Object.keys()
返回一个对象自己的可枚举属性组成的数组, , 不包括该对象原型链上的属性.
1 | let obj = { 100: 'a', 2: 'b', 7: 'c' } |
Object.assign()
将一个/多个源对象的自身所有可枚举属性拷贝给目标对象. 返回目标对象.
1 | let target = { a: 1, b: 2, c: 3 } |
语法
1 | Object.assign(target, ...source); |
使用 source 的 getter 和 target 的 setter.
示例
只有一个参数时, 直接返回该对象.
1 | const log = console.log; |
只有一个参数但是不是对象, 则会转成对象, 然后返回.
1 | log(Object.assign(2)); // [Number: 2] |
当source为null/undefined时, 不会报错.
1 | console.log(Object.assign({ a: 1 }, null)); // { a: 1 } |
复制对象
1 | let obj = { a: 1, b: 2 } |
深拷贝问题
浅拷贝: 仅仅拷贝对象的引用, 深拷贝: 完整拷贝对象, 是另一个新对象.
1 | const log = console.log; |
对象的深拷贝实现
使用 JSON.parse 与 JSON.stringify .
1 | const log = console.log; |
Object.defineProperty()
直接在一个方法上定义一个新属性, 或者修改一个对象的现有属性, 并返回此对象.
直接在Object构造器对象上使用此方法, 而不是对象实例.
1 |
|
语法:
obj: 要定义属性的对象
prop: 要定义或修改的属性名称或者Symbol
descriptor: 要定义或修改的属性描述符(数据描述符/存取描述符)
1 | Object.defineProperty(obj, prop, descriptor) |
返回值是被修改的对象.
描述符可拥有的键值以及默认值:
| configurable | enumerable | value | writable | get | set | |
|---|---|---|---|---|---|---|
| 默认值 | false | false | undefined | false | undefined | undefined |
| 数据描述符 | 可以 | 可以 | 可以 | 可以 | 不可以 | 不可以 |
| 存取描述符 | 可以 | 可以 | 不可以 | 不可以 | 可以 | 可以 |
模块化
概述
ES6之前, JS没有模块体系, 使用社区的模块加载方案, 分别是 CommonJS (用于服务器)和 AMD(用于浏览器) 两种, 都只能运行时确定模块的依赖关系.
运行时加载
1 | // CommonJS模块 |
只能在代码运行时, 才能整体加载fs模块(即加载fs的所有方法), 才能得到对象_fs, 再从这个对象读取三个方法.
ES6 引入了模块化,在编译时就能确定模块的依赖关系 + 输入和输出的变量, 成为浏览器和服务器通用的模块解决方案.
编译时加载
1 | // ES6模块 |
从fs模块加载3个方法, 其他方法不加载, called 编译时加载或者静态加载.
ES6 模块特点
自动开启严格模式
模块中可以导入导出各种类型的变量, 如函数/对象/字符串/布尔值/类等.
每个模块都有自己的上下文, 模块内声明的变量都是局部变量.
每个模块只加载一次, 再去加载该模块, 则直接从内存中读取.
用法
export 命令
ES6 的模块化 = 导出(export) 与导入(import)两个模块. 每个模块就是一个文件, 文件中的变量外界都不可以获取, 如果外界需要使用, 则需要使用export输出对应变量.
基本用法
输出变量
1 | // profile.js |
输出函数
1 | let addFunc = (a, b) => { |
输出类
1 | let myClass = class { |
接口与变量一一对应
export 命令规定的是对外的接口, 必须与模块内部的变量一一对应.
错误写法
对外输出的都是值, 不是接口
1 | // 错误写法一 |
正确写法
规定了对外的接口 a , 其他脚本可以通过这个接口, 取到值 5.
1 | // 写法一 |
动态绑定
export 输出的接口, 与对应的值是动态绑定关系, 即该接口对应的是 实时的值.
1 | // 接口 a 刚输出的值对应的是'aaa', 3 秒之后, 对应'bbb' |
模块顶层
export 命令可以出现在模块的任何位置,但必需处于模块顶层, 因为在块级作用域内, 就无法做静态优化(引入宏/类型检验)了.
1 | let a = () => { |
import 命令
import 后面的路径可以是绝对/相对, .js可以省略, 如果只是模块名不带有路径, 则需要配置文件.
只读属性
1 | import { name as myName, age } from './profile.js' |
但是如果导入的是对象, 修改其属性, 就是合法操作, 其他模块也可以读到改写后的值, 但是建议不要修改, 导入的变量全部当做只读.
1 | import {a} from './xxx.js' |
提升
import 命令具有提升效果, 会提升到整个模块的头部, 首先执行.
1 | // 不会报错 |
本质是因为, import是编译时加载, 而foo是运行时调用.
单例模式
import 语句会执行所加载的模块
1 | import 'lodash' |
上述代码仅仅执行 lodash 模块, 但是不输入任何值.
1 | import { a } from "./xxx.js"; |
静态执行特性
import 静态执行, 不能使用表达式和变量. 静态分析阶段, 这些语法都没法得到值.
1 | import { "f" + "oo" } from "methods"; |
模块的整体加载
使用 * 号指定一个对象, 所有输出值加载在这个对象上.
1 | // circle.js |
整体加载
1 | import * as circle from './circle'; |
export default 命令
导出匿名函数
为了让用户不阅读 export 的模块就能加载模块(不需要知道接口名), 则可以使用默认输出.
1 | // export-default.js |
导入该模块时, 可以指定任意名字. 此时 import 后面不需要加 {}.
1 | // import-default.js |
导出非匿名函数
这里 foo 函数在模块外被加载时, 等同于匿名函数.
1 | // export-default.js |
默认输出与正常输出
1 | // 第一组 |
一个模块只能有一个默认输出, 即 export default 命令只能使用一次, 因此 import 时才能不使用 {}.
default 变量
本质上, export default 命令是输出一个叫做 default 的变量或者方法, 系统允许你取任何名字.
1 | // module.js |
default 后面不能再跟变量声明语句.
1 | // right |
后面可以直接跟值, 相当于将后面的变量赋值给 default.
1 | // right |
最佳实践
避免使用 全局变量
new===eval()所有声明放在脚本或者函数的顶部, 顶部声明, 稍后使用
1
2
3
4
5
6
7
8
9
10
11// 在顶部声明
var firstName, lastName, price, discount, fullPrice;
// 稍后使用
firstName = "Bill";
lastName = "Gates";
price = 19.90;
discount = 0.10;
fullPrice = price * 100 / discount;声明变量时同时初始化
将数值/字符串/布尔值声明为原始值而非对象, 否则会拖慢速度
1
2
3let x = 'bill' // 字符串
let y = new String('bill') // 对象
console.log(x===y); // false请勿使用 new Object()
| 推荐使用 | 不建议 |
|---|---|
| {} | new Object() |
| [] | new Array() |
| function (){} | new Function() |
| “” | new String() |
| 0 | new Number() |
| false | new Boolean() |
| /()/ | new RegExp() |
意识到自动类型转换, 变量可以通过赋值改变其数据类型, 变量可包含不同的数据类型.
1
2
3let a = 'hello'
a = 5;
console.log(typeof a); // number为函数中的参数设置默认值,
undefined会破坏代码用
default来结束switch.
缺陷与错误
错误处理
原因
js代码中会出现错误, 由于编写代码/编译/用户输入等各种各样的原因.
处理
发生错误时, js引擎会停止并生成一个错误消息.
try与catch成对出现, finally是最后一定会执行的语句(可以没有).
throw抛出错误, 实际上就是抛出一个表示错误信息的字符串s, 因此可以自定义错误.
在catch中可以捕获s,实际上就是可以获得s的值并打印出来.
1 | test = (x) => { |
调试
操作
设置断点, 检查变量值, 浏览器内置调试器(按下F12, 选择console)
debugger关键字
代码会在debugger行停下, 并执行调试函数. 没有调试函数则不起作用.
与在调试工具中设置断点效果一样.
严格模式
原因
use strict
消除js语法的不合理之处, 保证代码安全; 增加编译效率;
使用
只能放在脚本或者函数的开头
具体内容
- 禁止使用未定义/声明的变量
- 禁止删除变量/函数
- 禁止变量重名
- 禁止使用八进制/转义字符
- 禁止对只读属性赋值
- 禁止删除不能删除的属性, 比如prototype
- 禁止变量名为eval/arguments
- 禁止使用右侧类似语句 with (Math){x = cos(2)};
- 禁止在作用域eval创建的变量被使用
- 禁止this指向全局对象
浏览器
网络与互联网
互联网, 连接所有实体计算机, 使得彼此之间可以互相发送数据.
通过互联网, 计算机之间可以发送二进制位. 为了传输能产生有效通信, 计算机之间必须知道这些位应该代表什么. 赋予比特序列的含义取决于想表达的事物的类型以及使用的编码机制.
网络协议 描述了网络上的通信方式, 有各种各样的协议, 用来做不同的事情.
超文本传输协议 (HTTP)是用于检索命名资源(例如网页或者图片之类的信息块)的协议.
发出请求的一方应该采用这样的格式描述请求方式, 想获取的资源以及使用的协议版本.
1 | GET /index.html HTTP/1.1 |
大多数协议都是基于其他协议构建的. HTTP 将网络视为一种类似于流的设备, 可以在其中放置二进制位并以正确的顺序到达目的地.
传输控制协议 (TCP) , 互联网中的所有设备均使用这个协议, 大多数通信都基于它.
TCP 的工作方式: 一台计算机等待并监听, 以便其他计算机与它建立连接. 一台机器为了同时监听不同类型的通信, 每个监听器占用的端口都不同. 大多数协议都指定了它默认使用的端口. TCP 连接在服务器与客户端之间建立的双向管道, 两端的机器都可以将数据放入其中, TCP 提供了网络的抽象.
Web
万维网 (World Wide Web) 是一组协议与格式(与互联网概念不同), 允许我们在浏览器中访问网页. Web 指的是这样的页面可以相互链接, 形成巨大网格. 换句话说, Web 就是浏览器上能访问的网页的集合.
成为 Web 的一部分, 需要其他机器能够到你这里请求文档, 则需要将计算机连接互联网并且使用HTTP协议监听80端口.
Web 上的每个文档都由统一资源定位符(URL)命名:
1 | http://qicai.fengniao.com/list_1437.html |
连接到互联网的机器会获得一个 IP 地址, 但是IP地址比较难记, 因此可以为 IP 地址注册域名, 域名就指向该 IP 地址, 对外则可以使用域名来提供服务.
在浏览器中键入该URL, 首先找到域名对应的IP, 然后使用 HTTP 协议与该 IP 对应的服务器建立连接, 然后请求相应的资源, 请求成功, 服务器会发回文档, 浏览器显示即可.
HTML
HTML : 超文本标记语言(Hypertext Markup Language), 是用于网页的文档格式. 包含文本以及为文本提供结构的标签.
文档以 <!doctype html> 开头, 告诉浏览器将页面解释为现代 html , 而不是过去使用的各种方言.
HTML 中的 \