JS高级
1. 访问对象属性(方法也是属性)的通用方式:obj['属性名']
1. 属性名包含特殊字符,如"-"、空格,访问:obj['content-type']
2. 属性名不确定:var name='age'; var value=18; obj[name]=value
2. IIFE:立即执行函数,用于隐藏实现、避免污染全局命名空间、编写JS模块
(function(){ ... })() ---> 即匿名函数自调用
3. this的指向
1. 任何函数本质上都是通过某个对象来调用的,如果没有显式指定,则默认是window;
2. 所有函数内部都有一个变量this,指向当前调用函数的对象;
3. functionName.call()/apply():第一个参数用于指定函数内部this的指向。
4. JS语句可以不加分号,但有2种情况必须加分号:
1. 小括号开头的前一条语句;
var a = 3;
(function() { ... })()
2. 中方括号开头的前一条语句;
var b = 4;
[1, 3].forEach(function)
3. 为了避免这2种错误,可以在行首加分号:;(function() { ... })()
原型
function Test() { ... }
1. prototype:显式原型(属性)
1. 定义函数时自动添加一个prototype属性,默认指向一个Object空对象,即原型对象;
Test.prototype instanceof Object --> true
Function.prototype instanceof Object --> true
Object.prototype instanceof Object --> false,Object除外
2. 原型对象中又有一个属性constructor,指向函数自身;
Test.prototype.constructor === Test ---> true
3. 给原型对象添加属性/方法,函数的所有实例对象自动拥有原型对象中的属性/方法。
2. __proto__:隐式原型(属性),ES6之前不能直接操作该属性
1. 创建实例对象时自动添加一个__proto__属性,默认值为构造函数的prototype属性值;
即:实例对象的隐式原型和构造函数的显式原型指向同一个原型对象;
var tes = new Test();
tes.__proto__ === Test.prototype ---> true
2. 函数也有隐式原型,而且所有函数(包括Object和Function)都是Function的实例;
Test/Object/Function.__proto__ === Function.prototype ---> true
3. 原型链:__proto__链
1. 原型对象中也有隐式原型__proto__,指向当前构造函数的父构造函数的原型对象;
tes.show() --> 自身 --> 沿着__proto__查找:tes.__proto__ 即Test.prototype
--> Test.prototype.__proto__ 即Object.prototype --> show()不存在,报错;
2. 所有对象/构造函数,包括Function,都直接/间接派生自Object,它是JS的顶层父类,
其原型对象就是原型链的尽头,因为Object.prototype.__proto__ === null
4. 访问对象属性/方法时,会自动到原型链上查找;而设置对象的属性值时,则不会涉及原型链,
如果对象中没有该属性/方法,则添加,否则就修改;
5. 一般情况下,方法定义在原型中,属性直接定义在通过构造函数;
6. A instanceof B 的判断的标准:如果B的显示原型在A的原型链上,则返回true.
函数高级
1. 全局执行上下文
1. 在执行全局代码前,将window确定为全局执行上下文;
2. 对全局数据进行预处理:var定义的全局变量会被提前声明(值为undefined),全局函数
会被提前定义,并把全局变量/函数添加为window的属性/方法;
3. this指向window;
4. 在执行代码时,才会对变量重新赋值,而定义函数的代码会直接跳过,因为在执行全局
代码之前,函数已经被提前定义了。
var test = 1;
function test() { ... }
test(); ---> 报错:test is not a function
5. 真正的执行过程:
var test; --> function test() { ... } --> test = 1; --> test();
2. 函数执行上下文
1. 调用函数、准备执行函数之前,创建对应的函数执行上下文;
2. 对局部数据做预处理:变量的提前声明,函数的提前定义,this的指向,形参的赋值等;
3. 闭包:内部函数引用外部函数的局部变量,外部函数执行完后,其局部变量仍存活于内存中;
function out() {
var a = 2;
function inner() {
console.log(++a);
}
return inner;
}
1. var f = out(); --> 变量f持有内函数inner()的引用,所以局部变量a仍在内存中;
2. out(); --> 没有任何引用内函数指向inner(),那么inner将成为垃圾对象,并被GC;
也即,局部变量a也不存在了;
3. 闭包的生命周期:在函数内部定义时就已经产生了,在内部函数成为垃圾对象时死亡;
var f = out(); --> f(); --> f = null; --> 死亡,释放内存;
4. 闭包容易造成内存泄露,应该及时释放。
4. 利用闭包自定义JS模块-1:myModule.js
function myModule() {
var msg = 1;
function showMsg() { ... }
return showMsg; --> 向外暴露
}
在html中引用:<script src="myModule.js" type="text/javascript">
var fn = myModule(); --> fn指向内部函数showMsg() --> 执行showMsg():fn();
如果暴露多个函数,则返回一个对象:return { showMsg: showMsg, }
var module = myModule(); --> 执行showMsg():module.showMsg();
5. 利用闭包自定义JS模块-2:myModule.js
(function(w) { --> 自执行函数
var msg = 1;
function showMsg() { ... }
w.myModule = { showMsg: showMsg, } --> 向外暴露
})(window) --> 为全局上下文window添加属性myModule
在html中引用:<script src="myModule.js" type="text/javascript">
myModule.showMsg(); --> myModule是window的属性,可以直接使用;
对象高级
1. 创建对象:
1. Object构造函数模式:var obj = new Object(); --> 然后为对象添加属性/方法;
2. 对象字面量模式:var obj = { ...(属性/方法) }
3. 工厂模式:动态返回一个对象的函数;
4. 自定义构造函数模式:var obj = new Person('Mack')
function Person(name) {
this.name = name;
this.show = function() { ... }
}
5. 构造函数+原型的组合模式:
function Person(name) { this.name=name; }
Person.prototype.show = function() { ... } --> 公共的方法定义在原型对象中
2. 原型链继承:子类的原型是父类的一个原型对象;
1. 定义父类构造函数,并在原型中添加方法;
function Supper() { this.supProp = 'Supper property'; }
Supper.prototype.showSupper = function() { ... }
2. 定义子类构造函数,让子类的原型指向父类对象,修正子类原型对象中的constructor,
让其指向子类构造函数,然后为子类的原型添加方法;
function Sub() { this.subProp = 'Sub property'; }
Sub.prototype = new Supper(); --> 更改显示原型的指向
Sub.prototype.constructor = Sub; --> 修正constructor的指向
本地存储
cookie,H5新增的localStorage、sessionStorage
cookie
1. 存储在浏览器的一段文本信息,最大容量4k,在同源的http请求时携带传递,损耗带宽;
2. cookie是在服务器端设置的,浏览器接收到cookie信息,便以键-值的形式存储在本地;
3. 出于安全考虑,跨域名请求不会携带cookie,这是由浏览器技术实现的安全策略;
4. 可设置访问路径和过期时间,只有此路径及其子路径才能访问此cookie。
localStorage
1. 容量最小5M,不会在http请求时携带传递,不需要服务器环境;
2. 在所有同源窗口中共享,数据始终有效,除非人为删除,可作为长期数据;
3. 设置:localStorage.setItem('key', 'value'); localStorage.key = value;
4. 获取:localStorage.getItem('key'); localStorage.key;
5. 删除:localStorage.removeItem('key');
sessionStorage
1. 容量最小5M,不会在http请求时携带传递,在同源的当前窗口关闭前有效;
2. 与localStorage合称为Web Storage,支持事件通知机制,可以将数据更新通知监听者;
3. iPhone的无痕浏览不支持Web Storage,只能用cookie.
JS与后台通信
AJAX:同源策略
1. JS原生支持AJAX,目的是让JS发送HTTP请求,且AJAX通信的过程是异步的;
2. 同源策略:基于安全考虑,AJAX只能请求同一域名下的资源,通常是JSON数据;
报错特征:No 'Access-Control-Allow-Origin' header is present
on the requested resource. Origin 'null' is therefore not allowed access.
3. AJAX执行的是同源策略,所以请求的url不需要域名,比如 url: 'js/user.json';
JSONP:跨域请求
1. 原理:<script src="..."> src链接的地址不受限制;
2. 在页面上定义的JS函数,script-src链接的外部JS文件也可以调用,那么,外部JS就可以
把数据以回调参数的形式传递回当前页面。
移动端JS事件
1. 移动端主要用手指操作,在JS中对应Touch事件:
1. touchstart/touchend/touchmove:手指放到屏幕上/离开屏幕/滑动时触发;
2. touchcancel:系统取消Touch事件时触发;
3. 移动端一般操作:点击、滑动、拖动,这三种操作一般组合使用Touch事件完成。
2. zeptojs:最初是为移动端提供一个精简的、类似jQuery的JS库,现在发展成一个轻量级的、
针对现代高级浏览器的JS库;
1. API类也似于jQuery,比如$(function(){ ... })、$(this)、$('#div') ...
2. 其touch模块封装了移动端常用的Touch事件,针对开发移动端的特定效果。
3. swiper.js:一个成熟稳定的、应用于PC端和移动端的滑动效果插件;
1. 一般用来触屏焦点图、触屏整屏滚动、幻灯片等效果;
2. 2.x版本支持低版本浏览器IE7,3.x放弃低版本浏览器,适用于移动端。
进程与线程
1. 作为浏览器脚本语言,JS主要与用户交互及操作DOM,这决定了JS是单线程的,效率高;
2. H5中的Web Workers可以多线程运行;
3. 浏览器是多线程运行的,Firefox、老版IE是单进程,新版IE、Chrome是多进程;
4. 浏览器内核:支撑浏览器运行的核心程序,由很多模块组成;
1. Chrome/Safari:webkit Firefox:Gecko IE:Trident
2. 360、搜狗等国内浏览器的内核:Trident+webkit
3. 内核模块-主线程:JS引擎模块(JS的编译与运行),HTML文档解析模块,DOM/CSS模块
(DOM/CSS在内存中的相关处理),布局和渲染模块(负责页面的布局和效果的绘制)...
4. 内核模块-子线程:定时器模块(管理定时器),事件响应模块(管理事件),网络请求模块
(AJAX请求)...
JS是单线程
1. 定时器机制:
1. 定时器并不能保证真正的定时执行,一般会延迟一点点,但也有可能延迟很长时间;
2. 定时器其实是在主线程中执行的,因为JS是单线程的,如果定时器执行前做耗时/阻塞的
操作,那么就可能造成定时器延迟很长时间。
2. JS代码分为初始化代码、回调代码,JS引擎执行代码的基本流程:
1. 先执行初始化代码:包括设置定时器、绑定监听、发送AJAX请求
2. 在后面的某个时刻才会执行回调代码;
3. 弹出alert,JS代码是阻塞的,计时也会被暂停,定时器随之被延迟,直到关闭alert后
才会恢复计时,执行定时器。
3. 事件驱动模型
1. event loop:事件轮询,事件包括定时器、DOM事件、AJAX
2. callback queue:事件的回调队列,存放待处理的回调函数;
3. 当事件发生时,管理模块会将回调函数及其数据添加到回调队列中,等待主线程执行。
Web Workers
1. JS是单线程的,在做耗时的操作时,如大量计算,Web界面就会卡死;
2. Web Workers是H5提供的一个JS多线程解决方案,子线程完全受主线程控制,由子线程去执行
耗时的计算,避免冻结主线程,但H5不允许子线程操作DOM,所以并没有改变JS单线程的本质;
3. 使用Web Workers:
1. 创建子线程执行的JS文件:worker.js
var onmessage = function(event) { --> 接收主线程发来的消息,var可省略
var data = event.data
postMessage('Hello Main') --> 向主线程发消息
}
2. 在主线程中的JS中发消息,并设置回调:
var worker = new Worker('worker.js')
worker.onmessage = function(event) { --> 监听子线程发来的消息
var data = event.data --> 获取子线程发来的数据
}
worker.postMessage('Hello Worker'); --> 向子线程发送消息
4. 主线程的全局上下文是window,而子线程的全局上下文DedicatedWorkerGlobalScope
1. DedicatedWorkerGlobalScope的属性/方法:
onmessage: null,postMessage: function,close: function ...
2. 子线程的全局上下文对象中没有alert()、document等,所以子线程中不能更新界面。
5. Web Workers的缺点:慢,不能跨域加载JS,不能操作DOM,有些浏览器不支持。
自动化与优化
1. less、sass、stylus:三种样式动态语言,属于CSS预处理语言,便于CSS的编写和维护;
1. 有类似于CSS的语法,赋予CSS动态语言的特性,如变量、继承、函数等;
2. 文件后缀分别是:x.less、x.scss、x.styl,并不能直接使用,需要编译成CSS文件;
3. 编译方式:软件编译如koala,或者用nodejs程序编译,如grunt、gulp
2. gulp比grunt更简洁、性能更高,gulp的常用插件:
1. 压缩JS代码gulp-uglify、less的编译gulp-less、CSS的压缩gulp-minify-css.
2. 自动添加CSS3前缀gulp-autoprefixer、文件重命名gulp-rename.
性能优化
1. 代码部署
1. 代码的压缩与合并:去除空格、换行等,可利用gulp工具的插件完成;
2. 图片、JS、CSS等静态资源存储在与主站不同的域名地址,避免在传输时携带cookie;
3. 权衡DNS查找次数,使用不同域名会增加DNS查找;与上述相违背,权衡处理;
4. 使用内容分发网络 CDN;使用GZIP压缩传输,压缩效率高;
5. 为文件设置Last-Modified、Expires和Etag,即本地缓存数据;
6. 避免不必要的重定向,在链接末尾手动加"/",如https://www.baidu.com/
2. HTML:避免空的src和href,不要在HTML中缩放图片。
3. CSS
1. 精简CSS选择器的层级,并把CSS放到顶部,避免@import的方式引入样式;
2. 使用Base64编码的图片数据取代图片文件,减少请求次数;
3. 使用CSS动画来取代JS动画,CSS动画是虚拟完成的,并不会改变页面结构;
4. 使用字体图标:https://fontawesome.com/icons
5. 使用CSS Sprite雪碧图,SVG图像;避免使用CSS表达式和滤镜。
4. JavaScript
1. 减少引用库的个数,使用模块化概念的requerejs/seajs异步加载JS;
2. JS在加载时会阻塞页面继续向下解析,理论上JS应该放在页面底部引用;
3. 避免全局查找,约束范围去查找;减少属性查找;尽可能使用原生方法;
4. 用switch代替复杂的if-else;减少语句数,比如多个变量声明可以合成一句;
5. 使用字面量表达式来初始化数组/对象,比如arr=[1,2,3]取代new Array(1,2,3);
6. 使用DocumentFragments/innerHTML取代复杂的元素注入;
7. 使用事件代理/委托;高频触发的事件设置函数节流;
8. 避免多次dom选择集,使用变量存储,var d = $('#div');
9. 使用Web Storage缓存数据;使用Array的join()取代字符串的"+"连接。