- A+
涉及到的链接:
JS——从入门到入狱(doge)
JavaScript简介
JavaScript是脚本语言,是一种轻量级的编程语言,插入HTML页面后可由所有现代浏览器执行;
由布兰登·艾克(Brendan Eich,Mozilla 项目、Mozilla 基金会和 Mozilla 公司的联合创始人)发明。
JavaScript 相当简洁,却非常灵活。开发者们基于 JavaScript 核心编写了大量实用工具,可以使 开发工作事半功倍。其中包括:
- 浏览器应用程序接口(API)—— 浏览器内置的 API 提供了丰富的功能,比如:动态创建 HTML 和设置 CSS 样式、从用户的摄像头采集处理视频流、生成 3D 图像与音频样本等等。
- 第三方 API —— 让开发者可以在自己的站点中整合其它内容提供者(Twitter、Facebook 等)提供的功能。
- 第三方框架和库 —— 用来快速构建网站和应用。
JavaScript的组成部分
ECMAscript
ECMAscript是JavaScript的核心,其规定了JS的语法规范
最近常用的一套规范为ECMAScript 6,也就是我们常说的ES6,随着ES的版本迭代,JavaScript也会更优化
有关ES6的新特性会在之后说
DOM
Document Object Model(文档对象模型)
DOM 模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点 (node),每个节点都包含着对象 (objects)。DOM 的方法 (methods) 让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。
开始的时候,JavaScript 和 DOM 是交织在一起的,但它们最终演变成了两个独立的实体。JavaScript 可以访问和操作存储在 DOM 中的内容,因此我们可以写成这个近似的等式:
API (web 或 XML 页面) = DOM + JS (脚本语言) 只是近似
Application Programming Interface(应用程序接口)
有关API的知识,我的暂时理解就是接口——供开发人员为了实现某个功能而去调用的、无需访问源码、无需理解其内部工作机制的东西
总之DOM会将web页面和脚本或者程序语言连接起来
API会在之后详细介绍
BOM
Browser Object Model(浏览器模型对象)
BOM模型提供了独立与内容的、可以与浏览器窗口进行互动的对象结构,BOM由多个对象构成,其中代表浏览器窗口的window对象是BOM的顶层对象,其他对象都是该对象的子对象。
window对象
Window 对象表示浏览器中打开的窗口。
如果文档包含框架(frame 或 iframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。
window常用的对象属性
- pageXOffset——设置或返回当前页面相对于窗口显示区左上角的 X 位置。
- pageYOffset——设置或返回当前页面相对于窗口显示区左上角的 Y 位置。
screenLeft,screenTop,screenX,screenY 声明了窗口的左上角在屏幕上的的 x 坐标和 y 坐标。
IE、Safari 和 Opera 支持 screenLeft 和 screenTop,而 Firefox 和 Safari 支持 screenX 和 screenY。
window对象常用方法
- onload()——当页面完全加载时,触发该事件
- onscroll()——当窗口滚动时触发该事件
- onresize()——当窗口大小发生变化时触发该事件
- setInterval()——按照指定的周期(以毫秒计)来调用函数或计算表达式
- setTimeout()——在指定的毫秒数后调用函数或计算表达式
- open()——打开一个新的浏览器窗口或查找一个已命名的窗口
Location对象
用于获得当前页面的地址 (URL),并把浏览器重定向到新的页面
location对象常用的属性
- location.herf = 'url地址'
- location.host——返回服务器名称和端口号
- location.pathname——返回目录和文件名
- location.search 返回?号后面的所有值
- location.portocol 返回页面使用的协议, http:或https
location对象常用的方法
- href——设置或获取整个 URL 为字符串
- reload()——重新加载页面地址。
- replace()——重新定向URL,不会在历史记录中生成新纪录
Navigator对象
navigator 对象包含有关访问者浏览器的信息
navigator对象常用的属性
- navigator.platform——操作系统类型;
- navigator.userAgent——浏览器设定的User-Agent字符串
- navigator.appName——浏览器名称;
- navigator.appVersion——浏览器版本;
- navigator.language——浏览器设置的语言;
- navigator.userAgent——最常用的属性,用来完成浏览器判断
screen对象
window.screen 对象包含有关用户屏幕的信息
history对象
history 对象包含浏览器的历史。为了保护用户隐私,对 JavaScript 访问该对象的方法做出了限制。
history对象常用的方法
- history.back() - 加载历史列表中的前一个 URL。返回上一页。
- history.forward() - 加载历史列表中的下一个 URL。返回下一页。
- history.go(“参数”) -1表示上一页,1表示下一页,或者具体页面的URL。
JavaScript的面向对象编程(JS OOP)
JavaScript所面向的对象,是一种基于原型的面向对象,与传统的面向对象有区别;
基于原型的语言(如 JavaScript)并不存在这种区别:它只有对象。基于原型的语言具有所谓原型对象 (prototypical object)的概念。原型对象可以作为一个模板,新对象可以从中获得原始的属性。任何对象都可以指定其自身的属性,既可以是创建时也可以在运行时创建。而且,任何对象都可以作为另一个对象的原型 (prototype),从而允许后者共享前者的属性。
对此我们需要理解原型。
原型(prototype)
JavaScript中几乎所有的对象都有一个原型对象,这个原型对象指向他的父对象,我们可以通过对象的proto属性访问到自身的原型对象。
事实上在JavaScript中,当访问一个对象的属性时,对象会先从自身的属性中查找,如果没有就往自身的原型对象中查找,再没在再上一级,直到找到该属性或者找到最顶层才停止。这一点就很像CSS
构造器
JavaScript中,一种定义对象的方法就是通过调用构造器方法。构造器本身是一个普通的函数,但是当调用构造器时在前面加上new关键字时,它就成了一个构造器函数。
let Student = function(name, number) { this.name = name; this.number = number; console.log('Student in'); } let student1 = new Student('张三', 1234); //Student in let student2 = new Student('李四', 5678); //Student in console.log('学生1是:' + student1.name + ' 编号是:' + student1.number); //学生1是:张三 编号是:1234 console.log('学生2是:' + student2.name + ' 编号是:' + student2.number); //学生2是:李四 编号是:5678
继承
JavaScript 通过将构造器函数与原型对象相关联的方式来实现继承。
基于类(Java)和基于原型(JavaScript)的对象系统的比较
基于类的(Java) | 基于原型的(JavaScript) |
---|---|
类和实例是不同的事物。 | 所有对象均为实例。 |
通过类定义来定义类;通过构造器方法来实例化类。 | 通过构造器函数来定义和创建一组对象。 |
通过 new 操作符创建单个对象。 |
相同。 |
通过类定义来定义现存类的子类,从而构建对象的层级结构。 | 指定一个对象作为原型并且与构造函数一起构建对象的层级结构 |
遵循类链继承属性。 | 遵循原型链继承属性。 |
类定义指定类的所有实例的所有属性。无法在运行时动态添加属性。 | 构造器函数或原型指定实例的初始属性集。允许动态地向单个的对象或者整个对象集中添加或移除属性。 |
JavaScript中的变量
变量是什么?
变量是用于存储数据的抽象空间,变量的本身并不是数据,而是装载数据的一个容器,变量使得计算机有了记忆。
你对‘苹果’的认识相当于数据,储存这些数据——形状,价格,味道等等——的空间就是变量
变量声明?
变量声明相当于告诉程序我要创建一个存储空间了,像是一个申请,根据声明关键词的不同,所申请的空间也不同,JavaScript中有三个变量声明以及一个无声明的形式。
JavaScript作用域
全局作用域、局部作用域
JavaScript的作用域为全局或者局部基本要靠function来区分,写于函数体内变量其作用域为局部作用域,作用范围仅在函数的大括号内;
声明于全局的变量即为全局变量,其无论在哪里——全局或者函数体内——都可以使用;
局部作用域在变量提升时不适用
作用域链
全局作用域和局部作用域中变量的访问权限,其实是由作用域链决定的。
每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链。作用域链是函数被创建的作用域中对象的集合。作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的最前端始终是当前执行的代码所在环境的变量对象(如果该环境是函数,则将其活动对象作为变量对象),下一个变量对象来自包含环境(包含当前还行环境的环境),下一个变量对象来自包含环境的包含环境,依次往上,直到全局执行环境的变量对象。全局执行环境的变量对象始终是作用域链中的最后一个对象。
标识符解析是沿着作用域一级一级的向上搜索标识符的过程。搜索过程始终是从作用域的前端逐地向后回溯,直到找到标识符(找不到,就会导致错误发生)。
总结:
- 每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链
- 执行环境有全局执行环境(全局环境)和局部执行环境之分
- 执行环境决定了变量的生命周期,以及哪部分代码可以访问其中变量
- 函数的局部环境可以访问函数作用域中的变量和函数,也可以访问其父环境,乃至全局环境中的变量和环境
- 全局环境只能访问全局环境中定义的变量和函数,不能直接访问局部环境中的任何数据
JavaScript的变量声明
var
var声明的变量可以不初始化赋值,其输出为undefined。
var声明的变量的作用域是全局或者是函数级的,定义于函数体内其作用域即为函数级;
var声明无法被单单的大括号限制其作用域;
var语句多次声明一个变量不仅是合法的,而且也不会造成任何错误;如果重复使用的一个声明有一个初始值,那么它担当的不过是一个赋值语句的角色;如果重复使用的一个声明没有一个初始值,那么它不会对原来存在的变量有任何的影响;
let
let需要JavaScript的严格模式:’use strict‘
let不能重复声明
let不能够预处理,也就是必须要赋初值,不然会报错
let声明的变量作用域是局部的,仅可函数内部使用
const
onst定义的变量不可以修改,而且必须初始化。关键字 const 有一定误导性;它不定义常量数组。它定义的是对数组的常量引用,所以数组元素仍旧可以修改
该变量是个全局变量,或者是模块内的全局变量;可以在全局作用域或者函数内声明常量,但是必须初始化常量
如果一个变量只有在声明时才被赋值一次,永远不会在其它的代码行里被重新赋值,那么应该使用const,但是该变量的初始值有可能在未来会被调整(常变量)
创建一个只读常量,在不同浏览器上表现为不可修改;建议声明后不修改;拥有块级作用域
const 代表一个值的常量索引 ,也就是说,变量名字在内存中的指针不能够改变,但是指向这个变量的值可能 改变
const定义的变量不可修改,一般在require一个模块的时候用或者定义一些全局常量
常量不能和它所在作用域内其它变量或者函数拥有相同名称
JavaScript的变量(函数)提升
JS的引擎机制为先编译在执行,编译过程中,编译会根据声明为其确定作用域,然后基于顺序为每一个语句进行编译、执行。
变量提升说的是var
一般来说,我们使用变量会经过“声明、赋值、使用”的流程,但是JS的变量或者函数由于声明提升可以先使用后声明
test(); function test() { a = 1; console.log(a); var a = 2; console.log(a); } /* 输出: 1 2 */
我们知道函数体的第一个a的声明一定提升了,但是提升到了哪里?是提升到了函数的第一行还是函数体外成为了全局变量?来慢慢探索
function test() { a = 1; console.log(a); var a = 2; console.log(a); } test(); console.log(a); /* 输出: 1 2 Uncaught ReferenceError: a is not defined */
这里由于重定义了a成为了块级作用域变量,导致在函数体外无法输出
test(); function test() { a = 1; console.log(a); // var a = 2; // console.log(a); } console.log(a); /* 输出: 1 1 */
test(); function test() { var a; a = 1; console.log(a); // var a = 2; // console.log(a); } console.log(a); /* 输出: 1 Uncaught ReferenceError: a is not defined */
就此我们发现a的声明提升到了函数体外,成为了全局变量
变量(函数)提升的好处
提高性能
在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍
该变量(函数),这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。
这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原
因,代码执行也更快了。
容错性提高
减少了由于疏忽导致的报错
规范的减少导致容错率提升,对我来说这不算好事
变量(函数)提升的坏处
增大空间的占用
变量提升会延长变量的生命周期,增大空间的占用
这个很好解释,全局变量的生命周期比局部变量的生命周期长,而变量提升会使原局部变量成为全局变量,导致函数结束时,本该结束生命周期、被销毁的变量仍旧存在
不易维护
由于变量提升很容易会将变量覆盖,导致后期的维护会变得困难
禁用变量提升
能够灵活使用变量提升很好,但是仍旧不推荐经常使用变量提升
let和const
为了解决上述问题,ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有块级作用域。let 和 const 是不存在变量提升的
use strict模式
严格模式的写法就是在代码编写之前加上"use strict",严格模式要求你不能使用未声明的变量,否则会报错。
还是建议大家尽可能得使用严格模式来编写javascript代码,以消除Javascript语法的一些不合理、不严谨之处,让自己成为一个更优秀的程序员。
JavaScript的数据类型
原始类型(基本数据类型)
存储空间大小一定
Number
- 整数与浮点数——1、3.14
- 八进制数与十六进制数——0768、0xff
- 指数——1e+5
- Infinity与NaN
- Infinity:表示一个超出JavaScript接受范围的数字,相当于无穷大,无穷小则为 -Infinity
- NaN:表示的使一种不符合规范但是仍旧属于数字类型的数字,例如当一个字符串与数字相乘时,返回的便是NaN,即Not a Number
String
String 是字符串类型,这一类型的数据主要指的是被反引号、单引号或双引号所引起来的、由任意个字符组成的字符序列
虽然单引号和双引号都可以使用,出于代码可读性方面的考虑,建议字符串风格保持一致,不要单引号和双引号交叉使用
ES6新增了反引号,也就是波浪线按键,用于简历模板字符串,特定场景会使用到
Boolean
Boolean 是布尔类型,这一类型的数据只有 true 和 false 两种值,主要用于关系运算和逻辑运算
undefined
这是 JavaScript 中的一个特殊值,当我们访问一个不存在的变量或未被初始化的变量时,程序就会返回一个 undefined 值
null
这也是 JavaScript 中的一个特殊值,通常是指没有值或值为空,不代表任何东西
引用类型(对象)
在JavaScript中,除了以上的基本数据类型,其他所有的值都被视为对象,某些环境会将null值视为一个对象
引用类型的数据占用内存空间的大小是不固定的,JavaScript解释器会在堆空间里为其分配内存,而不是栈,从而避免降低数据的存储速度
关于JavaScript中的this
每创建一个变量对象都会自动地创建一个执行环境。有关执行环境的内容在JavaScript在JavaScript中的变量——JavaScript作用域——作用域链
作用域链其实就相当于JavaScript中的执行环境链,而this的指向就是执行this语句的执行环境,其加上JavaScript的提升(JavaScript中的变量——JavaScript的变量提升),导致JS中的this相当魔性……(此说法有待考证)
其实可以将this的绑定视为以下几种情况
默认绑定
当一个函数没有明确的调用对象的时候,也就是单纯作为独立函数调用的时候,将对函数的this使用默认绑定:绑定到全局的window对象
凡是函数作为独立函数调用,无论它的位置在哪,它行为表现都和直接在全局环境中调用无异
隐式绑定
当函数被一个对象所“包含”的时候,我们称函数的this被隐式绑定到这个对象里面,这时候,通过this可以直接访问所绑定的对象里面的其他属性
显示绑定
能够改变this指向的call()、apply()以及bind()
call()
call 是函数原型上的方法,所有的实例都可以调用;
call会立即执行函数
call的实现原理:call会将需要改变this的函数挂载到context对象的临时方法上,然后调用context的临时方法,此时this就会指向context,最后删除掉临时方法
Function.prototype.newCall = function (context,...arg) { const ctx = context || window; // 没传或者传null默认是window ctx.fun = this; // 将call方法的目标函数挂载到目标对象身上 const res = ctx.fun(...args);// 执行fun并传参,fun是contex调用的 delete context.fun; // 删除fun return res } function test(name) { console.log(name + 'is' + this.age + 'years old'); } const obj = { age: 20 } test.newCall(obj, 'pillow');// pillow is 20 years old
apply()
apply 的实现原理和call相同,仅仅是传参的方式不同,并且传入的是一个数组
Function.prototype.newApply = function (context, arr=[]) { const ctx = context || window; ctx.fun = this // 将apply方法的目标函数挂到目标对象身上 const res = ctx.fun(...arr); delete context.fun; return res; }
bind()
bind不会立即执行函数,所以需要返回一个待执行的函数从而产生闭包
Function.prototype.myBind= function(bindObj){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]); return function(){ //apply绑定作用域,进行参数传递 return bindObj.apply(that,args) } } var test = function(a,b){ console.log('作用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = {value:'ok'} var newFn = test.myBind(obj,{value2:'also ok'}) newFn ('hello bind') // 作用域绑定 ok // testBind参数传递 also ok // 调用参数传递 undefined
Web API
待补充
Ajax
即Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)
传统页面如页面内容有更改,则需要刷新整个页面,而Ajax可以局部刷新网页
ajax主要是实现页面和web服务器之间数据的异步传输。
简单来说,不采用ajax的页面,当用户在页面发起请求时,就要进行整个页面的刷新,刷新快慢取决于服务器的处理快慢。在这个过程中用户必须得等待,不能进行其他操作。也就是同步的方式。客户端和服务端传递了很多不需要的数据。效率低,用户体验差。
XML
XMLHttpRequest(XHR)对象用于与服务器交互,我们通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL获取数据,并且虽然名字叫XMLHttpRequest,但实际上可以用于获取任何类型的数据。
……
同步和异步
为什么会有同步和异步
因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务,但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务
同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务
异步任务
异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务
异步机制
所有同步任务都在主线程上执行,行成一个执行栈
主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
主线程不断的重复上面的第三步
Promise规范
一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
promise必须实现then方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用,同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象