JavaScript
作用域和作用域
执行上下文
- 范围: 一段
或一个函数
- 全局: 变量定义,函数声明 一段
- 函数: 变量定义,函数声明,
this
,arguments
函数
作用域
js
在ES6
前无块级作用域
// Hoisting
if(true){
var name = 'wangcai'
}
函数(私有)作用域和全局作用域
var a=100;
function fn(){
var a = 200;
console.log(fn,a);
}
console.log('global',a);
fn();
作用域链
// 通俗地讲,当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链。
var a= 100;
function f1(){
var b = 200;
function f2(){
var c = 300;
console.log(a);
console.log(b);
console.log(c);
}
}
私有作用域执行顺序
正常来讲程序自上往下执行
-
形参赋值
- 全局环境不能直接访问局部环境
- 如果
function
(私有作用)没有形参赋值, 私有作用域中变量无声明修饰符,该变量就上级作用域找赋值;如果有形参赋值,私有作用域不会变量提升.
-
变量提升
- 变量提升: 只是提升变量的声明,并不会把赋值也提升上来。
- ⭐
var a
;在 全局作用域下声明的变量,也是相当于给window
设置了一个对象属性,而且两者之间建立了映射的机制 <==>window.a=undefined
; - 方法变量提升是会把函数体提上上来
-
let
,const
声明不存再变量提升,如果let
,const
同一作用域存在重复声明,浏览器有自我检测机制,代码(一开始)直接报错,不存在解释执行
fn()
function fn(){console.log(1)}
fn()
function fn(){console.log(2)}
fn()
var fn = 10
fn() //报错, 下面就不会再执行
function fn(){console.log(3)}
fn()
function fn(){console.log(4)}
fn()
//最终输出: 4 4 4 fn is not function
//exp2
let a=10,b=10;
function fn(){
// console.log(a,b); //会报错, a is not defined
let a = b =20; // 这样写语法 === let a =20; b = 20;
console.log(a,b);
}
fn()
console.log(a,b)
//输出:10 10
// 10 20
// exp3
var a = 12,b= 13,c = 14;
function fu(a){
/****
* 1.形参赋值
* a = 12
* 2.变量提升
* var b;
* => 在私有作用域中, 只有下面两种情况是私有变量
* A:声明过的变量 (带var/function)
* B:形参也是私有变量
* 剩下的都不是自己的私有变量,都需要基于作用域链的机制向上查找
*/
console.log(a,b,c);
var b = c = a = 20;
console.log(a,b,c);
}
fn(a) // 执行就是把全局a的**值**12 当实参传递给函数的形参 ==>fn(12),
// 注意:如果传的是引用类型,传递的值是栈内存地址
console.log(a,b,c);
//exp 4
var ary = [12,13];
function fn(ary){
console.log(ary); // =>[12,13]
// 虽然这里的ary 是私有的, 但其指针还是与公有的ary是同一个对内存数据
ary[0]=100;
ary = [100]; // 这里是重新赋值了,指针改变
ary[0] = 0;
console.log(ary) //=>[0]c
}
fn(ary); // 引用类型,传递的值是栈内存地址
console.log(ary); // =>[100,13]
in
:检测某一个属性是否隶属的于这个对象(不管是私有属性还是共有属性,只要有这个属性结果就是true
)
hasOwnProperty
:检测某一个属性是否隶属的于这个对象(只有这个属性是私有的才可以)
var a;
console.log("a" in window);
var v='Hello World';
alert(v); //Hello World
var v='Hello World';
(function(){
alert(v); //hello world
})()
var v='Hello World';
(function(){
alert(v); //undefined
var v='I love you';
})()
等同
var v='Hello World';
(function(){
var v; // 变量提升
alert(v); //undefined
v='I love you';
})()
// 当解析器读到 if 语句的时候,它发现此处有一个变量声明和赋值,
// 于是解析器会将其声明提升至当前作用域的顶部(这是默认行为,并且无法更改),
// 这个行为就叫做 Hoisting。
var a = 1;
function foo() {
if (!a) {
var a = 2;
}
alert(a);
};
foo();
// 等同
var a = 1;
function foo() {
var a;
if (!a) {
a = 2;
}
alert(a);
};
foo();
(function(){
var a='One';
var b='Two';
var c='Three';
})()
// 等同
(function(){
var a,b,c;
a='One';
b='Two';
c='Three';
})()
函数提升:
function
是可以在被执行的位置之后定义,JS实际运行时会首先将它提升到顶部
函数提升是把整个函数都提到前面去。只有函数才会创建新的作用域(函数声明才会被提升,表达式声明不会被提升)
// 在我们写js code 的时候,我们有2中写法,一种是函数表达式,另外一种是函数声明方式。
// 我们需要重点注意的是,只有函数声明形式才能被提升。
// 函数声明方式提升【成功】
// 代码如下:
function myTest(){
foo();
function foo(){
alert("我来自 foo");
}
}
myTest();
// 函数表达式方式提升【失败】
// 代码如下:
function myTest(){
foo();
var foo =function foo(){
alert("我来自 foo");
}
}
myTest(); //foo is not a function
⭐
当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和他再那执行没有关系, 和他在哪创建(定义)的有关系, 在哪创建的,他的上级作用域就是谁!
var a = 1;
function fn(){
console.log(a)
var a = 5
console.log(a)
a++
var a
fn3()
fn2()
console.log(a)
function fn2(){
console.log(a)
a = 20
}
}
function fn3(){
console.log(a)
a = 200
}
fn()
console.log(a)
//输出
undefined
5
1
6
20
200
循环事件绑定
严格模式
arguments
是一个对应于传递给函数的参数(实参)的类数组对象,映射关系(赋值瞬间关联),能建立映射的就建立映射,不能建立的,后面不管怎么操作都无法关联;
⭐在严格模式下不存在映射
"use strict"
function fn(x,y,z){
var arg = arguments;
console.log(x); // ==> 10
arg[1] = 30;
// 这里再赋值是没有用的
// 虽然argments:
// 0:10
// 1:30
// length:1 ⭐没错还是1
// ....
// 但与形参不会再映射
console.log(arg);
console.log(y); // ==> undefined
y=40;
console.log(arg[1]);// 还是==30;
}
fn(10);
/**
* argments:
* 0:10
* length:1
~function(){
"use strict"
function fn(x){
arguments[0]=100;
console.log(x); // => 10 不存在映射
}
fn(10);
}();
* callee:指向当前执行的函数
* ...
**/
"use strict"
function getInfo(name, age, sex){
console.log('name:',name);
console.log('age:', age);
console.log('sex:', sex);
console.log(arguments);
arguments[0] = 'valley';
console.log('name', name);
}
getInfo('Tim', 2, '男');
//name: Tim
//age: 2
//sex: 男
//{
// 0: Tim,
// 1: 2,
// 2: 男,
// length: 3,
// callee: f(),
//}
//name: Tim ==>非严格模式下是 valley
第一,严格模式下无法再意外创建全局变量
逻辑计算
// 1. 逻辑或赋值 false 取 或后面值
var a = false || 1; // =>1
// 2. 逻辑与相反
var b = false && 2; // =>false
// 逻辑与一般回调函数中用到
function fn(callback){
// 如果传递的值是个函数, 我们才让其执行
// if(typeof callback === "function"){
// callback;
// }
// 简写版(不严谨,但用的多)
// 默认callback要不然就传函数,要不然就不穿
callback && callback();
}
fn(function(){
// xxx
})
3.逻辑与和逻辑或的混合模式
// 优先级: 逻辑与的优先级高于逻辑或
方法执行运算
注意数组的参数引用
1.非严格模式下自行函数如果不是.出来的 就是属于window
var b = 2;
var c = 5 + (b++); // c=7; b=2; 这里注意了b++加不加括号都是顺序计算 这里括号没意义
类:具备某些共同特征的实体的集合
实例:某一个类别具体的一个事务
js有5中简单数据类型(也称为基本数据类型): Undefined
、Null
、Boolean
、Number
和String
。还有1中复杂的数据类型————Object
,Object
本质上是由一组无序的名值对组成的。
其中Undefined
、Null
、Boolean
、Number
都属于基本类型。Object
、Array
和Function
则属于引用类型,String
有些特殊:
因为字符串具有可变的大小,所以显然它不能被直接存储在具有固定大小的变量中。由于效率的原因,我们希望JS只复制对字符串的引用,而不是字符串的内容。但是另一方面,字符串在许多方面都和基本类型的表现相似,而字符串是不可变的这一事实(即没法改变一个字符串值的内容),因此可以将字符串看成行为与基本类型相似的不可变引用类型
var x = 1;
var y = x;
x.name = 'hanna'; // 基本类型无法添加属性
console.log(y); //1
console.log(x.name); //undefined
typeof
会根据对象类型返回对应的类型字符串, 但是有几个缺点:
对于数组、函数、对象来说,其关系错综复杂,使用 typeof
都会统一返回 “object
” 字符串,
null
→object
NaN
→number
undefined
→ undefined
那么此时我们有第二个方法可以使用, 是 ES3
中的 Object.prototype.toString
方法,我们可以用Object.prototype.toString.call(obj)
检测对象类型:
何时使用 ==
和 ===
if(obj.a==null){
// 这里相当于 obj.a == null || obj.a == undefined , 简写形式
// 这是Jquery 源码中推荐写法, 其他都用 ===
// 注意 == 不能用于 未定变量判断
if(xxx==null){} //报错 xxx is not defined
if
判断转换只有 0、NaN
、空字符串、null
、undefined
会转换为false
,其他都为true
console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(Math));//[object Math]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]
为什么得用Object.prototype.toString.call
,因为toString
为Object
的原型方法,而Array
、Function
等类型作为Object
的实例,都重写了toString
方法,不同的对象类型调用toString
方法时,根据原型链的知识,调用的是对应的重写之后的toString
方法(Function
类型返回内容为函数体的字符串,Array
类型返回元素组成的字符串.....),而不会去调用Object
上原型toString
方法(返回对象的具体类型),所以采用obj.toString()
不能得到其对象类型,只能将obj
转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object
上原型toString
方法。
原型链
prototype
: 显示原型
__proto__
: 隐式原型
function Fn(){
this.x = 100;
this.y = 200;
var z = 300;
this.getX = function(){
console.log(this.x);
}
}
Fn.prototype.y = 500;
Fn.prototype.getX = function(){
console.log(this.x);
}
Fn.prototype.getY = function(){
console.log(this.y);
}
var f1 = new Fn; // 当没传参时,写不写()都行
var f2 = new Fn();
console.log(f1.x) //undefined 只有函数类的this. 属性或方法 实例才能继承
console.log(f1.getX === f2.getX) // false 两个不同的实例的 **私有** 方法
console.log(f1.getY === f2.getY) // true 如果私有属性没有 就往上级查找
console.log(f1.__proto__.getY === Fn.prototype.getY) // true
console.log(f1.__proto__.getX === F2.getX) // false
console.log(f1.constructor) // FN; f1是实例没有constructor,往上查找 prototype.constructor ==> Fn;
console.log(Fn.prototype.__proto__.constructor); // Fn.prototype.__proto__ === Object.prototype;
f1.getX();// 100 this:f1 ==>console.log(f1.x)
f1.__proto__.getX() // undefined f1.__proto__ === Fn.prototype
f2.getY();// 200 this:f2 ==>console.log(f2.y)
Fn.prototype.getY() // 500
Fn.prototype.getX() // undefined
原型重定向
EXP1
function fun(){
this.a = 0;
this.b = function(){
alert(this.a);
}
}
fun.prototype = {
b: function(){
this.a = 20;
alert(this.a);
},
c: function(){
this.a = 30;
alert(this.a);
}
}
var my_fun = new fun();
my_fun.b(); // 0
my_fun.c(); // this => my_fun.a = 30 ; 30
// 结果:0 30
my_fun.a
用来设置私有属性
my_fun.__proto__.a
用来设置公有属性
为什么要重定向原型
项目开发中方便批量扩展属性与方法的时候.
原型重定向导致的问题
- 自己开辟的堆内存中没有
constructor
属性,导致类的原型构造函数缺失(解决:自己手动在堆内存中增加constructor
属性) - 当原型重定向后,浏览器默认开辟的那个类原型堆内存会被释放掉,如果之前已经存储了一些方法或属性,都会丢失(所以:内置累的原型不允许重定向到自己开辟的堆内存,因为内置类的原型上存在很多属性方法,重定向后都没了,这样是不被允许的;但浏览器对内置类有保护机制)
私有属性
: 自己对内存中存储的属性,相对自己来说是私有
公有属性
: 自己基于proto找到的属性,相对自己来说是公有的
关于this
的两道题
this
指向判断 MDN
的解释:当前执行代码的环境对象
- 元素绑定事件,方法中的
this
是当前操作的元素 - 方法名签是否有点,有点,点前面是谁
this
就是谁,没有this
是window
(严格模式下是undefined
) - 构造函数执行,方法中的
this
是当前类的一个实例
全局环境,
this
就代表Window
对象。
var name = 'zhar';
function say(){
console.log(this.name);//zhar
}
say();
对象环境,
this
指向对象本身。
var obj = {
name : "zhar",
say : function(){
console.log(this.name);//zhar
}
}
obj.say();
构造函数环境,
this
会指向创建出来的实例对象
function Person() {
this.name = 'zhar';
}
var p = new Person();
console.log(p.name);
事件对象,在
DOM
事件中使用this
,this
指向了触发事件的DOM
元素本身
li.onclick = function(){
console.log(this.innerHTML);
}
EXP1
var fullName = 'language';
var obj = {
fullName: 'javascrtpt',
prop: {
getFullName: function(){
return this.fullName;
}
}
};
console.log(obj.prop.getFullName()); // this => obj.prop; obj.prop没有fullName属性; undefined
var test = obj.prop.getFullName;
console.log(test()); // this => window.fullName(非严格模式); language
EXP2
var name = 'window';
var Tom = {
name: 'Tom',
show: function(){
console.log(this.name); // 4. 'window'
},
wait: function(){
var fun = this.show; // 2. => this: Tom
fun(); // 3. => this: window
}
};
Tom.wait(); // 1. => this: Tom
当this
碰到return
时
function fn()
{
this.user = '小J';
return {};
}
var a = new fn;
console.log(a.user); //undefined
function fn()
{
this.user = '小J';
return function(){};
}
var a = new fn;
console.log(a.user); //undefined
function fn()
{
this.user = '小J';
return 1;
}
var a = new fn;
console.log(a.user); //小J
function fn()
{
this.user = '小J';
return undefined;
}
var a = new fn;
console.log(a.user); //小J
//如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
function fn()
{
this.user = '小J';
return undefined;
}
var a = new fn;
console.log(a); //fn {user: "小J"}
//还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。
function fn()
{
this.user = '小J';
return null;
}
var a = new fn;
console.log(a.user); //小J
call apply
是什么? Function
原型上的方法
作用? 改变this
的指向
exp
// 数组拼接
var a1 = [1,2,3]; var a2 = [4,6,5];
[].push.apply(a1,a2);
// 类型版判
Object.prototype.toString.apply(new Date);
// 数组最大小值
Math.max.apply(null,a1);
// 位数组的转换
var a ={0:'xxx',1:'xxxx',2:'xx',length:3};
var b = [].slice.call(a);
b.forEach(i=>console.log(i));
// 构造继承
function A(){}
function B(){
A.apply(this,arguments)
}
// 简便分割字符串
// 原: 分割成数组再转换
console.log('abc'.split(',').join(','))
=> // ⭐ 当使用apply 或 call 传入的第一个值传入的简单类型时,
// 会自动转换包装对象
console.log([].join.call('abc',','))
[].forEach.call('abc',i=>{
console.log(i)
})
// 一般框架 严格模式下兼容问题
(function(){
'use strict'
}).call(this)
npm
nrm
是专门用来管理和快速切换私人配置的registry
建议全局安装
npm install nrm -g --save
nrm
有一些默认配置,用nrm ls
命令查看默认配置,带*
号即为当前使用的配置
nrm ls
也可以直接输入以下命令查看当前使用的是哪个源
nrm current
切到源http://r.cnpmjs.org/
,命令:nrm use
源的别名,即
nrm use cnpm
模块化
优点
- 避免命名冲突(减少全局命名空间污染);
- 更好的分离,按需加载;
- 代码复用;
- 高可维护维护.
全局函数模式:将不同的功能封装成不同的全局函数(最原始的写法)
缺点:命名冲突,全局污染,其他js可随意访问修改变量
let msg = "module1"
function foo(){
console.log('foo()',msg)
}
function bar(){
console.log('foo()',msg)
}
namspace模式: 简单对象封装
缺点:其他js还是可随意访问修改变量
let obj = {
msg:'module2',
foo:function(){
console.log("xxxx")
}
}
IIFE模式:匿名函数自调用(闭包) 立即执行函数
// jquer写法
(function(win){
let msg = 'xxasdf';
function fn(){
console.log('fn()',msg);
}
win.module3 = {fn};
})(window)
IIFE模式增强:引用依赖: 现代模块实现的基石
问题:
- 请求过多
- 依赖模糊
- 难以维护
//
(function(win,$){
let msg = "module4";
function foo(){
console.log('foo()',msg);
}
win.module4 = foo;
&('body').css('backgruond','red');
})(window,jQuery);
CommonJS Nodejs 使用
每个文件可当做一个模块
前端打包工具出现,CommonJS前端也可以使用
AMD requsit
AMD
是 RequireJS
在推广过程中对模块定义的规范化产出。
CMD
是 SeaJS
在推广过程中对模块定义的规范化产出。
类似的还有 CommonJS Modules/2.0
规范,是 BravoJS
在推广过程中对模块定义的规范化产出。
还有不少⋯⋯
这些规范的目的都是为了 JavaScript
的模块化开发,特别是在浏览器端的。
目前这些规范的实现都能达成浏览器端模块化开发的目的。
区别:
对于依赖的模块,
AMD
是提前执行,CMD
是延迟执行。不过RequireJS
从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD
推崇 as lazy as possible.-
CMD
推崇依赖就近,AMD
推崇依赖前置。看代码:// CMD define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此处略去 100 行 var b = require('./b') // 依赖可以就近书写 b.doSomething() // ... }) // AMD 默认推荐的是 define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 a.doSomething() // 此处略去 100 行 b.doSomething() ... })
虽然AMD
也支持 CMD
的写法,同时还支持将 require
作为依赖项传递,但 RequireJS
的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。
-
AMD
的API
默认是一个当多个用,CMD
的API
严格区分,推崇职责单一。比如AMD
里,require
分全局require
和局部require
,都叫require
。CMD
里,没有全局require
,而是根据模块系统的完备性,提供seajs.use
来实现模块系统的加载启动。CMD
里,每个API
都简单纯粹。
ES6 出现,想同一现在所有模块化标准
模块化只是代码的写法问题, 没必要弄这么多标准, 标准是越统一越好,越简单越好.
语法:import
export
(注意有无default
)
环境:babel
编译ES6
语法,
模块化工具: Webpack
(功能强大)始于2012年,rollup
(功能专一,打包文件比较小,Vue
,React
)
Webpack
始于2012年,由 Tobias Koppers发起,用于解决当时现有工具未解决的的一个难题:构建复杂的单页应用程序(SPA
)。特别是 Webpack
的两个特性改变了一切:
- 代码拆分(
Code Splitting
) 使你可以将应用程序分解成可管理的代码块,可以按需加载,这意味着你的用户可以快速获取交互性的网站,而不必等到整个应用程序下载和解析完成。当然你可以手动来完成这项工作,那么祝你好运。 - 静态资源(
Static assets
) 如图像和CSS
可以导入到你的应用程序中,而且还能够被作为依赖图中的另一个节点。再也不用关心你的文件是否放在正确的文件夹中,再也不用为文件URL
增添hash
而使用hack
脚本,因为Webpack
会帮我们处理这些事情。
Rollup
则是由于不同的原因被创建的:利用 ES2015
巧妙的模块设计,尽可能高效地构建出能够直接被其它 JavaScript
库引用的模块。其他的模块打包工具 – 包含 Webpack
– 通过都是将每个模块封装在一个函数中,将它们放在一个包中,通过浏览器友好的 require
实现,最后逐一执行这些模块。如果您需要按需加载,webpack 这类的打包工具非常合适。否则有点浪费,如果你有很多模块,它会变得更糟。
ES2015
模块启用了一种不同的方法,这是真是 Rollup
使用的。所有的代码都放在同一个地方,然后一次性执行,从而生成更简洁、更简单的代码,从而启动更快。您可以 自己使用 Rollup REPL
来查看它 。
但是有一个权衡:代码拆分(Code Splitting
)是一个更加棘手的问题,在撰写本文时,Rollup
还不支持。同样的,Rollup
也不支持模块的热更新HMR。而对于使用 Rollup
的人来说,最大的痛点可能是 – 它能处理大多数 CommonJS
文件(通过 插件 ),然而有些东西根本不能转译为 ES2015
,而 Webpack
能处理所有你丢给它的事情。
抉择: 对于应用使用 Webpack
,对于类库使用 Rollup
Webpack
HMR 热更新实现流程
-
Webpack
编译期,为需要热更新的entry
注入热更新代码(EventSource
通信) → 最新的webpack-dev-server
,改成用WebSocket
协议了,基于sockjs - 页面首次打开后,服务端与客户端通过
EventSource
建立通信渠道,把下一次的hash
返回前端 - 客户端获取到
hash
,这个hash
将作为下一次请求服务端hot-update.js
和hot-update.json
的hash
- 修改页面代码后,
Webpack
监听到文件修改后,开始编译,编译完成后,发送build
消息给客户端 - 客户端获取到
hash
,成功后客户端构造hot-update.js
script
链接,然后插入主文档 -
hot-update.js
插入成功后,执行hotAPI
的createRecord
和reload
方法,获取到Vue
组件的render
方法,重新render
组件, 继而实现 UI 无刷新更新。
虚拟MOD
MVVM
- Model-模型、数据
- View - 视图、模板(视图和模型是分离的)
- ViewModel - 连接Model和View(连接器,桥的作用),View可以通过事件绑定印象到Model,Model可以通过数据绑定影响到View
MVVM是MVC的一种创新,MVC主要用于后端
组件化 模块化
组件化 | 模块化 |
---|---|
就是"基础库"或者“基础组件",意思是把代码重复的部分提炼出一个个组件供给功能使用。 | 就是"业务框架"或者“业务模块",也可以理解为“框架”,意思是把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。 |
使用:Dialog,各种自定义的UI控件、能在项目或者不同项目重复应用的代码等等。 | 使用:按照项目功能需求划分成不同类型的业务框架(例如:注册、登录、外卖、直播.....) |
目的:复用,解耦。 | 目的:隔离/封装 (高内聚)。 |
依赖:组件之间低依赖,比较独立。 | 依赖:模块之间有依赖的关系,可通过路由器进行模块之间的耦合问题。 |
架构定位:纵向分层(位于架构底层,被其他层所依赖) | 架构定位:横向分块(位于架构业务框架层)。 |
总结
- 组件相当于库,把一些能在项目里或者不同类型项目中可复用的代码进行工具性的封装。
- 而模块相应于业务逻辑模块,把同一类型项目里的功能逻辑进行进行需求性的封装。
vue
Vue与jQuery区别
vue 数据与视图分离, 以数据驱动视图,只关心数据变化,DOM操作备份装, jQuery 直接在代码操作DOM.
VUE 三要素
-
响应式:
vue
如何监听到Data
的每一个属性变化? →Object.defineProperty
var obj = {}; var _name='张三'; Object.defineProperty(obj,"name",{ get:function(){ console.log('get',_name); // 监听 return _name; }, set:function(newVal){ console.log('set',_name); // 监听 _name = newVal; } }); //Vue 监听模拟 var vm = {} var data = { name: 'zhangsan', age: 20 } var key, value for (key in data) { (function (key) { Object.defineProperty(vm, key, { get: function () { console.log('get', data[key]) // 监听 return data[key] }, set: function (newVal) { console.log('set', newVal) // 监听 data[key] = newVal } }) })(key) }
-
模板引擎:
vue
的模板是如何解析,指令如何处理,模板是什么?render
函数执行解析模板vnode
- 本质:字符串
- 有逻辑,如
v-if
v-for
- 与
HTML
格式很像,但有很大区别 - 最终还是要转换为
HTML
来显示
-
渲染:
vue
的模板如何渲染成HTML
? 以及渲染过程-
render
函数执行vnode
-
UpdateComponent
→patch
-
Vue 实现流程
第一步 解析Vue
模板字符串成render
函数, 模板中用到的data
中的属性,都变成了JS
变量模板中的 v-model
v-for
v-on
都变成了JS
逻辑,render
函数执行后返回vnode
第二步 响应式开始监听, 用Object.defineProperty
修改变量的get
/set
方法,实现响应式,开始监听,并将 data
的属性/methods
的方法等代理到 vm
上(从data.list
代理到vm.list
上,这样render
函数才能用到那个变量with(this){...list}// vm.list
)
第三步:首次渲染,执行render
函数,获得vnode
,patch
转化成真实DOM
,显示页面,且通过get
收集订阅者,绑定依赖(执行 render
函数,会访问到 vm.list
vm.title
,会被响应式的 get
方法监听到,只有被get
监听到的data
才会在set
的时候响应式更新,data
中有很多属性,有些被用到,有些可能不被用到,避免不必要的重复渲染)
第四步:data
属性变化,被响应式set
监听到(上一步的data
,不被get
监听到的,这一步不会被set
监听到,即怎么修改都不会变化),触发updateComponent
,重新执行render
函数,生成新的vnode
,与旧vnode
执行diff
算法对比,重新patch
更新到html
页面中
简化
- 解析模版,通过render函数生成vnode格式的JS
- 响应式监听数据变化(getter,setter)
- 渲染页面,绑定依赖
- data数据变化,触发re-render更新视图
React vs Vue
本质区别
-
Vue
- 本质是MVVM
框架,由MVC
发展而来 -
React
- 本质是前端组件化框架,由后端组件化发展而来
但这并不妨碍他们两者都能实现相同的功能
模板区别
-
Vue
- 使用模板(最初由angular
提出) -
React
- 使用JSX
(模板与js
混在一起,未分离) - 模板语法上,我更加倾向于
JSX
- 模板分离上,我更加倾向于
Vue
组件化的区别
-
React
本身就是组件化,没有组件化就不是React
-
Vue
也支持组件化,不过是在MVVM
上的扩展 - 查阅
Vue
组件化的文档,洋洋洒洒很多(侧面反映) - 对于组件化,我更加倾向于
React
,做的彻底而清晰
两者共同点
- 都支持组件化
- 都是数据驱动试图
国内使用,首推 vue 。文档更易读、易学、社区够大
如果团队水平较高,推荐使用 React 。组件化和 JSX
hybrid
-
hybrid
是客户端和前端的混合开发 -
hybrid
存在的核心意义在于快速迭代,无需审核 -
hybrid
实现流程: 服务端做好静态页面包,客户端检查包版本,如果客户端版本低,就获取服务端静态包, 客户端拿到前端静态页面,以文件形式存储在app
中
客户端 使用file
协议 通过webview
加载静态页面
使用 NA
:体验要求极致,变化不频繁(无头条的首页)
使用 hybrid
:体验要求高,变化频繁(如头条的新闻详情页)产品型
使用 h5
:体验无要求,不常用(如举报、反馈等页面) 运维型
优点: 体验好,快速迭代,无需审核
缺点: 开发成本高,运维成本高
JS与客端通信
String 自带函数
substr()
含头含尾(废弃),substring()
含头不含尾,slice()
也是含头不含尾
JS 执行过程 ⭐⭐
event loop
事件循环机制 单线程 任务队列
先由上到下执行同步任务,异步任务挂起,等同步任务处理后再执行异步任务(如果同步任务没有结束→死循环,异步任务是不会执行的.)
- 主线程 由上到下执行 (同步任务)
- 异步任务:
- 微任务:
Promise
mutationObserver
interSectionObserver
- 宏任务:
setTimeout
setInterval
requestAnimationFrame
- 微任务:
JS为什么是单线程?
避免DOM
渲染冲突
- 浏览器需要渲染
DOM
-
JS
可以修改DOM
-
JS
执行的时候,浏览器DOM
渲染会暂停 - 两段
JS
也不能同时执行(都修改DOM
就冲突了) -
webworker
支持多线程,但不能访问DOM
同步异步区别:
- 同步会阻塞代码(
alert
),异步不会(setTimeout
)
异步问题
- 没按书写方式执行,可读性差
-
callback
中不容易模块化
setTimeout(function(){
console.log(1)
},0);
new Promise(r=>{
console.log(2);
r();
console.log(3);
}).then(data=>{
console.log(4);
});
console.log(5);
// 2 3 5 4 1
$.ajax({
url:'xxx',
success:function(){
console.log(1)
}
})
setTimeout(function(){
console.log(2)
},1000)
setTimeout(function(){
console.log(3)
})
console.log(4)
// 这里分两种情况,如果ajax请求数据的数据在1内就返回了就
// 4,3,1,2
// 如果请求的数据没在1内返回
// 4,3,2,1
Promise ⭐⭐
Promise
对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。Promise
可以让异步操作写起来,就像在写同步操作的流程,而不必一层层地嵌套回调函数。
特点
- 对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变
为什么要使用Promise
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数; 解决回调地狱(异步).
原型方法:
Promise.prototype.then(resolved,rejected)
注意点:
-
then
有两个参数,第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数 -
then
返回的是一个新的Promise
, 将以回调的返回值来resolve
.
Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection
)或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
Promise.prototype.finally()
finally
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作。该方法是 ES2018
引入标准的。
缺点
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
面试题
///实现一个简单的Promise
function Promise(fn){
var status = 'pending'
function successNotify(){
status = 'fulfilled'//状态变为fulfilled
toDoThen.apply(undefined, arguments)//执行回调
}
function failNotify(){
status = 'rejected'//状态变为rejected
toDoThen.apply(undefined, arguments)//执行回调
}
function toDoThen(){
setTimeout(()=>{ // 保证回调是异步执行的
if(status === 'fulfilled'){
for(let i =0; i< successArray.length;i ++) {
successArray[i].apply(undefined, arguments)//执行then里面的回掉函数
}
}else if(status === 'rejected'){
for(let i =0; i< failArray.length;i ++) {
failArray[i].apply(undefined, arguments)//执行then里面的回掉函数
}
}
})
}
var successArray = []
var failArray = []
fn.call(undefined, successNotify, failNotify)
return {
then: function(successFn, failFn){
successArray.push(successFn)
failArray.push(failFn)
return undefined // 此处应该返回一个Promise
}
}
}
HTML
块元素 大多为结构性标记
特点
- 独占一行(从上到下排版);
- 可以设置
Css
盒子模型所有属性;再不设置宽高时,宽继承父级,宽有内容决定; - 可以嵌套其他元素(p不能嵌套p,dt不能嵌套其他元素)
属性名 | 描述 |
---|---|
section |
文档节 |
nav |
导航 |
header |
页眉 |
article |
文章 |
aside |
文章侧栏 |
footer |
页脚 |
div |
|
h1 ~ h6 |
标题 |
p |
段 |
ul > li |
无须列表 |
ol > li |
有序列表 |
dl > dt > dd |
自定义列表 |
table > tr > td |
表格 |
form |
表单 |
行类元素 大多为描述性标记
特点
- 从左到右一行显示;
- 不可以设置宽高;(但可设置
margin
、padding
左右值) - 不设置宽高时,宽高是又内容决定的
- 编辑代码时,行内元素出现回车或者换行时,会默认又间隙(解决:父级(
body
)设置font-size:0
) - 不可以嵌套块级元素
- 基线对齐问题
元素
属性名 | 描述 |
---|---|
span |
内联容器 |
a |
|
strong b |
|
em i |
|
label |
行类块级元素
特点
- 从左到右一行显示;
- 可以设置Css盒子模型所有属性;不设置宽高时,宽高是又内容决定的
- 编辑代码时,行内块级元素出现回车或者换行时,会默认又间隙(解决:父级(
body
)设置font-size:0
)
元素
| 属性名 | 描述 |
|img
|
|input
|
|textarea
| 文本域|
|audio
|
|video
|
视口概念
属性名 | 取值 | 描述 |
---|---|---|
width | 正整数 或?device-width | 定义视口的宽度,单位为像素 |
height | 正整数 或?device-height | 定义视口的高度,单位为像素,一般不用 |
initial-scale | [0.0-10.0] | 定义初始缩放值 |
minimum-scale | [0.0-10.0] | 定义缩小最小比例,它必须小于或等于maximum-scale设置 |
maximum-scale | [0.0-10.0] | 定义放大最大比例,它必须大于或等于minimum-scale设置 |
user-scalable | yes/no | 定义是否允许用户手动缩放页面,默认值yes |
Css
盒子模型
标准盒子模型
高宽只有content
IE盒子模型(怪异模型)
高度是border
+padding
+content
设置方法:
/* 盒子模型最终宽高默认是不计算 边框与内边距的
* content-box:最终宽高 = 内容的宽高
* border-box:最终宽高 = border + padding + 内容的宽高
*/
box-sizing:content-box; //默认
box-sizing:border-box; //IE模型
margin-top
传值问题:
块级元素嵌套时,当父级元素没有设置padding-top
和border-top
时,自己元素设置了margin-top
值时,这个值会直接传递父级元素
解决方法:
父级元素设置:overflow:hidden
,超出隐藏;
margin
兼容问题:
一个元素设置了margin-top
,另一个元素设置了margin-bottom
,这两个元素不会相加,会取最大值(这两个元素时同级,平级关系的时候,⭐ BFC解决)
三角形
//实心
div {
width: 0;
height: 0;
border: 40px solid;
border-color: transparent transparent red;
}
// 箭头三角形
#blue:after {
content: "";
position: absolute;
top: 2px;
left: -38px;
border-width: 0 38px 38px;
border-style: solid;
border-color: transparent transparent #fff;
}
js 获取盒子模型对于宽高
dom.style.width
/height
or
dom.currentStyle.width
/height
or
window.getComputedStyle(dom).width
/heigth
定位
相对定位 relative
当盒子本身发生位置改变时,又不影响其他元素,就用相对定位
- 不脱离文档流
- 当位置发生改变时,原来位置还在占用
- 层级大于文档流内的其他元素(会覆盖在其他元素之上)
- 参照物是本身
- 给绝对定位当作参照物(常用)
- 同时设置
top
和bottom
值时top
生效,同时设置left
和right
时left
生效
绝对定位 absoluto
不设置参照物时,参照物是
body
-
人为设置参照物时
- 参照物必须时父级元素
- 父级元素必须带有定位属性
- 同级之间不能设为参照物
脱离文档流的
不设置四个方向值时,这个绝对以为元素前面有其他元素,就会默认排在这个元素的后面
同时设置
top
和bottom
值时top
生效,同时设置left
和right
时left
当设置了宽高为100%时,将继承参照物宽高
改变定位的层级关系 z-index
- 当定位属性时平级时,哪个元素在上面就设置哪个属性的
z-index
值(前提时设置定位属性) - 当定位的父级元素同时设置
z-index
值时,子元素与父元素相比较时不生效的.
display
行类块级元素转换
-
block
块级元素- 独占一行
- 可设置宽高
- 可以嵌套
-
inline
: 行内- 不可设置宽高
- 有间隙
- 基线对齐问题
- 只能从左到右
-
inline-block
: 行内块- 有间隙
- 基线对齐问题
- 只能从左到右
-
float
:浮动- 脱离文档流(父级找不到子集),相当于来到了第二层级,平行默认文档流
- 不在设置宽高时,宽高有内容决定
- 所有元素都可以设置
float
无论是img
,a
,span
,div
... - 设置是浮动属性,这个元素相当于是行内块级元素(可以设置宽高)
- 行类元素、行内块级元素和文字围绕着浮动元素排布(图文混编)
清除float: 面试点
- 设置父级高度(不常用),不管子集有没有元素,高度一定的.
- 给父级元素设置
overflow:hidden;
把子集元素拉回到文档流内 - 清除浮动
clear:both
,必须保证三个前提- 使用这个这个元素必须是块级元素
- 使用这个属性的元素必须放再最后一个浮动元素后置,使用
:after伪类
搞定 - 使用这个属性的元素不能带有float属性
.clear:after{
display:block;
content:"";
clear:both; /**高版浏览器**/
}
.clear{
*zoom:1;/**兼容低版本浏览器**/
}
BFC
就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
BFC``布局规则:
- 内部的
Box
会在垂直方向,一个接一个地放置。 -
Box
垂直方向的距离由margin
决定。属于同一个BFC
的两个相邻Box
的margin
会发生重叠 - 每个元素的
margin box
的左边, 与包含块border
-
box
的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。 -
BFC
的区域不会与float box
重叠。 - 计算
BFC
的高度时,浮动元素也参与计算
DOM事件
- Dom事件级别(版本)
Dom0
element.onclick = function(){}
Dom2element.addEventListener('click',function(){},false)
Dom3element.addEventListener('keyup',function(){},false)
Dom1 制订的时候没有设计与事件相关的东西,所有没有Dom1 ,Dom2与3写法没差别,Dom3 多了许多事件类型; true:冒泡,false:捕获
- Dom事件模型(捕获,冒泡)
捕获:上到下 (
window
→document
→html
→body
→目标元素)
冒泡:下到上 (目标元素→body
→html
→document
→window
)
- Dom事件流
事件流:捕获(阶段)→ 目标阶段→冒泡(阶段)
Dom捕获流程(参考2)
-
Event对象常用应用
-
event.preventDefault()
:阻止默认行为(a标签:设置了click事件,阻止其默认跳转) -
event.stopPropagetion()
:阻止冒泡 -
event.stoplmmediatePropagetion()
:事件优先级设置 -
event.currentTarget
: 当前绑定事件元素,父级元素 (事件代理) ⭐ -
event.target
:获取具体点击元素 ⭐
-
-
自定义事件
//与其他事件结合使用 var eve = new Event('test'); ev.addEventListener('test', function () { console.log('test dispatch'); }); setTimeout(function () { ev.dispatchEvent(eve); //调用是对象,不是名称 }, 1000);
HTTP 协议类 参考
特点
- 简单快速:每个资源都是固定的,直接请求获取
- 无连接:不会保持连接,
HTTP 0.9
和1.0
使用非持续连接:限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接。HTTP 1.1
使用持续连接:不必为每个web
对象创建一个新的连接,一个连接可以传送多个对象,采用这种方式可以节省传输时间 - 无状态:单次
http
协议是不能区分连接者身份,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。 - 灵活:
HTTP
允许传输任意类型的数据对象。正在传输的类型由Content-Type
加以标记。
HTTP报文
A(请求报文 Requst)-->B(请求行)
A--> C(请求头 -- key/value值请求参数)
A--> D(空行)
A--> E(请求体)
B--> F(http方法)
B--> G(页面地址)
B--> H(http协议)
B--> I(http协议版本)
F--> J(GET - 获取资源)
F--> K(POST - 传输资源)
F--> L(PUT -更新资源)
F--> M(DELETE - 删除资源)
F--> N(HEAD -获取报文首部)
Z{响应报文 Respose}--> Y(状态行)
Z-->X(响应头)
Z-->T(空行)
Z-->S(响应体)
Y-->R(协议/协议版本)
Y-->Q(状态码)
Y-->P
GET与POST区别 99%的人都理解错了HTTP中GET与POST的区别
- GET在浏览器回退时是无害的,而POST会再次提交请求。⭐
- GET产生的URL地址可以被Bookmark,而POST不可以。
- GET请求会被浏览器主动cache,而POST不会,除非手动设置。⭐
- GET请求只能进行url编码,而POST支持多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- GET请求在URL中传送的参数是有长度限制的,而POST么有。⭐
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。⭐
- GET参数通过URL传递,POST放在Request body中⭐
GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
GET和POST还有一个重大区别,简单的说:
GET产生一个TCP数据包;POST产生两个TCP数据包。
长的说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。
HTTP 状态码
1xx: 指示信息-表请求已经接收,继续处理
-
2xx: 成功 - 请求已被成功接收
200.
OK
: 客户端请求成功206.
Partial Content
:客户发送一个带有Range
头的GET
请求,服务器完成了它 (某部分,播放视频音频文件) -
3xx: 重定向 - 要完成请求必须进行更进一步的操作
301.
Moved Permanenly
:永久重定向, 所请求的页面已经转移至新的url
302.
Found
:临时重定向 所有请求页面已经零时转移至新的url
304.
Not Modified
:客户端缓存可以继续使用, 客户端有缓冲的文档并发出了一个条件性的请求,服务器告诉客户,原来的缓存的文档还可以继续使用 -
4xx: 客户端错误 - 请求哟语法错误或请求无法实现
400.
Bad Requset
客户端请求有语法错误,不能被服务器理解401.
Unauthorized
请求未授权,这个状态码必须和WWW-Authenticate
报文域一起使用403.
Forbidden
服务器收到请求,但是拒绝提供服务(不能直接访问,只能服务器去访问)404.
Not Found
请求资源不存在 -
5xx: 服务器错误 - 服务器未能实现合法的请求
500.
Internal Server Error
服务器错误,发生了不可预期的错误,原理来的缓存的文档还可以继续使用503.
Server Unavailable
请求未完成, 服务器零时过载或宕机,一段时间后可能恢复正常
HTTP 持久连接与管线化 (版本1.1后)
-
什么是持久连接? (keep alive模式)
HTTP1.1规定了默认保持长连接(HTTP persistent connection,也有翻译为持久连接);数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
HTTP 1.1版本支持持久连接 1.0版本不支持
与非持久连接的区别:
持久连接使客户端到服务器端连接持续有效,避免了重新建立连接
大大减少了连接的建立以及关闭时延。HTTP连接是建立在TCP协议之上的,建立一条TCP连接需要三次握手,TCP连接关闭时需要四次挥手。这些都是需要时间的。
-
什么是管线化 → 客户端并行发送请求,服务器并发多个响应
管线化机制须通过永久连接(persistent connection)完成,仅HTTP/1.1支持此技术(HTTP/1.0不支持)
在使用持久连接的情况下,某个连接消息的传递类似于
请求1 → 响应1 → 请求2 → 响应2
管线化:某个连接上的消息变成了类似这样
请求1 → 请求2 → 请求3 → 响应1 → 响应2 → 响应3
⭐
- 那么持久连接和管线化的区别在于:持久连接的一个缺点是请求和响应式是顺序执行的,只有在请求1的响应收到之后,才会发送请求2,而管线化不需要等待上一次请求得到响应就可以进行下一次请求。实现并行发送请求
- 只有GET和HEAD要求可以进行管线化,而POST则有所限制
- 初次创建连接时也不应启动管线机制,因为对方(服务器)不一定支持HTTP/1.1版本的协议
- HTTP1.1要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败,而且现在很多服务器端和代理程序对管线化的支持并不好,现代浏览器Chrome和Firefox
跨域
只要协议、域名、端口有任何一个不同,就是跨域。
为什么不能跨域?
浏览器有一个同源策略,用来保护用户的安全。
跨域的解决方案
-
JSONP 原理:script标签引入一个js文件,下载文件
- 优点:兼容性好,在很古老的浏览器中也可以用,简单易用,支持浏览器与服务器双向通信。
- 缺点:只支持GET请求,且只支持跨域HTTP请求这种情况(不支持HTTPS)
CORS 一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
服务器代理.
-
H5 PostMessage
// postMessage // 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息 Bwindow.postMessage('data', 'http://B.com'); // 在窗口B中监听 Awindow.addEventListener('message', function (event) { console.log(event.origin); console.log(event.source); console.log(event.data); }, false);
-
iframe hash
// 利用hash,场景是当前页面 A 通过iframe或frame嵌入了跨域的页面 B // 在A中伪代码如下: var B = document.getElementsByTagName('iframe'); B.src = B.src + '#' + 'data'; // 在B中的伪代码如下 window.onhashchange = function () { var data = window.location.hash; };
-
Websocket【参考资料】http://www.ruanyifeng.com/blog/2017/05/websocket.html
var ws = new WebSocket('wss://echo.websocket.org'); ws.onopen = function (evt) { console.log('Connection open ...'); ws.send('Hello WebSockets!'); }; ws.onmessage = function (evt) { console.log('Received Message: ', evt.data); ws.close(); }; ws.onclose = function (evt) { console.log('Connection closed.'); };
原型链
创建对象有几种方法
// 第一种方式:字面量
var o1 = {name: 'o1'};
var o2 = new Object({name: 'o2'});
// 第二种方式:构造函数,任何函数都可以用作于构造函数,
// 只要去用New关键字去操作生成新的实例对象,这个函数就是构造函数
var M = function (name) { this.name = name; };
var o3 = new M('o3');
// 第三种方式:Object.create
var p = {name: 'p'};
var o4 = Object.create(p);
M.prototype.say = function () {
console.log('say hi');
};
var o5 = new M('o5');
// new 运算符原理
var new2 = function (func) {
var o = Object.create(func.prototype);
var k = func.call(o);
if (typeof k === 'object') {
return k;
} else {
return o;
}
};
console.log(o3 instanceof M)// true
console.log(o3 instanceof Object)// true
什么是原型链:
一个实例对象向上找构造该实例的相关联对象,该相关联的对象又往上找创造他的它的原型对象,以此类推,一直到object.prototype,这个链条就是原型链
作用:
公用方法
面向对象
/**
* 类的声明
*/
var Animal = function () {
this.name = 'Animal';
};
/**
* es6中class的声明
*/
class Animal2 {
constructor () {
this.name = 'Animal2';
}
}
/**
* 实例化
*/
console.log(new Animal(), new Animal2());
/**
* 借助构造函数实现继承
* 缺点: 无法继承 父类的prototype的公有方法
*/
function Parent1 () {
this.name = 'parent1';
}
Parent1.prototype.say = function () {
};
function Child1 () {
Parent1.call(this);
this.type = 'child1';
}
console.log(new Child1(), new Child1().say());
/**
* 借助原型链实现继承
* 缺点: 父类的应用类型属性是公用的,都可以修改
*/
function Parent2 () {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2 () {
this.type = 'child2';
}
Child2.prototype = new Parent2();
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);
/**
* 组合方式
*/
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3 () {
Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
/**
* 组合继承的优化1
* @type {String}
*/
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4 () {
Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5, s6);
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor);
/**
* 组合继承的优化2
*/
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5 () {
Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
⭐⭐⭐
function Elem(id){
this.elem = document.getElementById(id);
}
Elem.prototype.on = function(type,fn){
var elem = this.elem;
elem.addEventListener(type,fn);
return this;
}
Elem.prototype.html = function(hl){
var elem = this.elem;
if(hl){
elem.innerHTML = hl;
return this;
}
else{
return elem.innerHTML;
}
}
var em = new Elem("ad_t2");
em.on('click',function(){console.log('click click')})
.html("yooo~")
.on('mouseover',function(){console.log("mouse mouse over voer....")});
js Class与普通构造函数有何区别
- js构造函数
function MathHandle(x,y){
this.x = x
this.y = y
}
MathHandle.prototype.add = function(){
return this.x + this.y
}
var m = new MathHandle(1,2)
console.log(m.add())
- class基本语法
class MathHandle{
constructor(x,y){
this.x = x
this.y = y
}
add(){
return this.x + this.y
}
}
const m = new MathHandle(1,2)
console.log(m.add())
三. 语法糖
在上述两段代码中分别加入如下代码,运行。
console.log(typeof MathHandle) // 'function'
console.log(MathHandle.prototype.constructor === MathHandle) //true
console.log(m.__proto__ === MathHandle.prototype) //true
运行结果一致。我认为,class是构造函数的语法糖。
四、继承
-
构造函数形式的继承
//动物 function Animal(){ this.eat = function (){ console.log('Animal eat') } } //狗 function Dog() { this.bark = function (){ console.log('Dog bark') } } Dog.prototype = new Animal() var hashiqi = new Dog() hashiqi.bark() hashiqi.eat()
-
class继承
class Animal { constructor(name){ this.name = name } eat(){ alert(this.name + ' eat') } } class Dog extends Animal { constructor(name){ super(name) //super就是被继承的对象的constructer } say(){ alert(this.name + ' say') } } const dog = new Dog('哈士奇') dog.say() dog.eat()
五、总结
- class在语法上更贴近面向对象的写法。
- class实现继承更加易读易理解。
前端安全类
CSRF
CSRF(Cross-site request forgery):跨站请求伪造。
CSRF:又称XSRF,冒充用户发起请求(在用户不知情的情况下),完成一些违背用户意愿的请求(如恶意发帖,删帖,改密码,发邮件等)。XSS
页面性能
提升页面性能方法:
资源压缩,减少HTTP请求
非核心代码异步加载 → 异步加载的方式 → 异步加载的区别
利用浏览器缓存 → 缓存的分类 → 缓存的原理
使用CDN
-
预解析DNS
-
异步加载的方式:
- 动态脚步加载,创建动态js标签
- defer → 是在HTML解析完之后才会执行,如果多个,按照加载的顺序依次执行
- async → 是在加载完成之后立即执行,如果是多个,执行顺序和加载顺序无关
-
缓存的分类: http 请求头
- 强缓存
Expires
(绝对时间): Thu, 21 Jan 2017 18:25:02 GMT
⭐Cache-Control
: max-age=360(秒) - 协商缓存
Last-Modified
if-Modified-Since
⭐Etag
If-None-Match
- 强缓存
错误监控类
-
即时运行错误:代码错误
- try...catch
- window.onerror
-
资源加载错误(不会冒泡,但可捕获)
- object.onerror
- performance.getEntries()
- Error 事件捕获
window.addEventListener('error',function(e){ (new Image()).src="http://xxxxx上传反馈错误的信息地址"; },true);
-
上报错误基本原理
- ajax上报
- Image对象上报 ⭐