JS高级
this指向分析
指向
-
直接调用,指向window
-
通过对象调用,指向对象
-
call/apply
总结:跟位置无关,跟调用方式有关。只有在执行的时候this指向才会被确定
绑定规则:
-
默认绑定
// 独立函数调用,this指向window function foo(){ console.log(this) } foo() // window // 跟位置无关,跟调用方式有关 var obj = { foo:function(){ console.log(this) } } var baz = obj.foo baz() // window // 高阶函数 function foo1(fn){ fn() } foo1(obj.foo) // window // 严格模式下,独立调用的函数中的this指向的是undefined "use strict" var obj = { foo:function(){ console.log(this) } } var baz = obj.foo baz() // undefined
-
隐式绑定
var obj = { foo:function(){ console.log(this) } } obj.foo() // obj
-
显示绑定
-
call(obj,[item1],[item2])
-
apply(obj,[item1,item2])
var obj={ name:'hyf' } funciton foo(){ console.log(this) } foo.call(obj) // obj
-
bind
:创建一个绑定函数BF,怪异函数对象。永久绑定var obj={ name:'hyf' } funciton foo(){ console.log(this) } var f = foo.bind(obj,[item1],[item2]...)
-
-
new
/* 1. 创建一个新的空对象 2. 将this指向这个空对象 3. 执行函数体重的代码 4. 如果函数没有返回其他对象时,默认返回这个对象 */ function Foo(){ console.log(this) } var foo = new Foo() // Foo {}
内置函数的调用绑定
- forEach(fn,this): 默认绑定window, 可以通过第二个参数绑定this
- setTimeout(): this指向window
- el.coclick: this指向 el
优先级
默认绑定 < 隐式绑定 < 显示绑定 < new绑定。new不可以和apply/call一起使用。bind优先级大于call和apply
null/undefined
function Foo(){
console.log(this)
}
Foo.call(null) // window, 忽略显示绑定,使用默认规则
Foo.apply(undefined) // window,忽略显示绑定,使用默认规则
间接函数引用
var obj1 = {
foo:function(){
console.log(this)
}
}
var obj2 = {
name:'hyf'
}
obj2.foo = obj1.foo
obj2.foo() // obj2
(obj2.foo = obj1.foo)() // window
箭头函数
- 不会绑定this、arguments
- 不能作为构造函数
var foo = (arg) => {
// do some thing
}
var obj = (arg) => ({name:33}) // obj = {name:33}
浏览器原理
网页解析过程
- DNS域名解析
- 通过IP地址,与服务器三次握手
- 获取URI资源
- html下载到浏览器中
浏览器渲染过程
-
下载并解析index.html,生成DOM树
-
下载并解析CSS,生成样式规则,CSSOM,CSS对象模型
-
DOM Tree+ CSSOS 生成渲染树,render Tree
-
在Reader Tree上计算节点尺寸和位置等信息,进行布局Layout
-
绘制Paint,将每个frame转为屏幕上的实际的像素点
link元素不会阻塞DOM Tree 但会阻塞Reader Tree构建
Reader Tree 与 DOM Tree不是一一对应的,
回流和重绘
- 回流reflow: 对节点的大小、位置修改重新计算称为回流
- DOM结构发生改变,添加或删除
- 改变布局(width,height,padding,font-size)
- resize(修改了窗口的尺寸)
- 调用getComputedStyle方法获取尺寸、位置信息
- 重绘 repaint: 重新渲染
- 修改背景色,文字颜色,边框颜色,样式
回流一定会引起重绘
-
如何避免回流
-
修改样式时尽量一次性修改
-
尽量避免频繁的操作DOM, DocumentFragment
const list = document.querySelector('#list') const fruits = ['Apple', 'Orange', 'Banana', 'Melon'] const fragment = new DocumentFragment() fruits.forEach((fruit) => { const li = document.createElement('li') li.textContent = fruit fragment.appendChild(li) }) list.appendChild(fragment)
-
尽量避免通过getComputedStyle获取尺寸,位置等信息
-
对某些元素使用position的absolute或者fixed, 开销相对较少
-
合成层
- 默认情况下,标准流中的内容都是被绘制在同一个图层中的
- 创建新的合成层
- 3D transforms
- video、canvas、iframe
- opacity动画转换时
- position: fixed
- will-change:一个实验性的属性,提前告诉浏览器元素可能发生哪些变化
- animation或transition设置了opcity、transform
script元素与页面解析
遇到script,会停止DOM解析,先加载执行script脚本
- defer属性:
- 不会阻塞DOM Tree构建过程。
- 在DOMContentLoaded之前执行
- 多个脚本顺序执行
- 建议放到head中,让浏览器先加载
- async属性:
- 不阻塞页面
- 脚本完全独立
- 不能保证多个脚本顺序
- 不能保证在DOMContentLoaded之前或之后执行
JS原理
Webkit = WebCore + JavaScriptCore
V8引擎原理
V8引擎是由C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等,V8可以独立运行,也可以嵌入到任何C++应用程序中
- Parse模块,会将JS代码转为AST(抽象语法树),解释器并不认识JS代码。如果函数没有被调用是不会被转为AST树的
- lgnition是一个解释器,会将AST转为ByteCode(字节码),同时会搜集TurboFan优化所需的信息(如函数参数类型信息),如果函数只调用一次,lgnition会解释执行ByteCode
- TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码
- 如果一个函数被调用多次,那么会被标记为热点函数,就会经过TurboFan转换为优化的机器码,提高代码的执行性能
- 但是,机器码实际上也会被还原为ByteCode,这是因为函数在执行中传入的类型改变,之前优化的机器码并不能准确的处理,就会逆转为字节码
JS执行上下文
整体执行流程
-
js代码执行之前,初始化全局对象Global Object(GO)
-
该对象在堆内存中创建,所有作用域都可访问
-
包含
Date、Array、String、Number、setTimeout、setInterval
等 -
还有
window
指向自己
-
-
每一个执行上下文都会关联一个VO(Variable Object,变量对象),变量和函数声明都会被添加到这个VO对象中
-
全局代码被执行的时候,VO就是GO对象
-
AO对象:函数执行上下文,会创建一个AO(Activation Object)
- 这个AO会使用
arguments
作为初始化,并且初始值是传入的参数 - 这个AO对象会作为执行上下文的VO来存放变量的初始化
- 这个AO会使用
var message = 'aaa'
function bar(){
var message = 'bb'
}
var num1= 0
var num2= 1
var result = num1+num2
函数会被先创建
函数代码的多次执行
函数在第一次执行完成以后,会被栈移除,AO对象的移除需要看情况(垃圾回收)
作用域和作用域链
函数在创建的时候,作用域链就被确定了,跟调用位置无关
作用域链综述:
-
首先,在代码执行之前,创建GO(Global Object)--VO(Variable Object)对象
在GO对象中 message = undefined foo = 0x001(指向一个函数对象 Function Object),这个函数对象中包含一些属性,arguments/name/length/[[scopes]]... [[scopes]] 中包含0:Global Object bar = undefined test = undefined
-
代码执行,
运行 var message = 'global message' 时,GO对象的message被赋值为'global message' 调用 foo() 时,运行 foo函数代码,此时创建 foo VO(Variable Object)即AO(Activation Object) foo AO中 name = undefined , bar = 0x002(指向一个函数对象 Function Object) 这个函数对象中包含一些属性,arguments/name/length/[[scopes]]... [[scopes]] 包含 0:Global Object 返回 bar函数的内存地址 0x002 将0x002赋值给bar变量 此时GO中的bar = 0x002
-
代码执行bar()
调用函数bar(),运行0x002中的代码,创建test=0x003 函数(Function Object)arguments/name/length/[[scopes]]... [[scopes]] 中包含0:foo AO, 1:Global Object 打印name, 寻找name顺序 0--->1 返回0x003 将0x003赋值给GO中test变量
面试题
// 1.
var n = 100
function foo(){
n = 200
}
foo()
console.log(n) // 200
// 2
var n = 100
function foo(){
console.log(n) // undefined
var n = 200
console.log(n) // 200
}
foo()
// 3
var n = 100
function foo1(){
console.log(n)
}
function foo2(){
var n = 200
console.log(n) // 200
foo1()
}
foo2()
/*
分析:
1. 创建GO,n=undefined,foo1=0x001(arguments/name/[[scopes]]:{"0":GO}),foo2=0x002(arguments/name/[[scopes]]:{"0":AO,"1":GO}) AO 包含 n = undefined
2. 调用foo2() 执行foo2代码体中的var n = 200, foo2 AO n = 200,打印200,调用foo1()
3. foo1执行代码,打印n,n的作用域只用GO,所以打印n为100
*/
// 4
var a = 100
funciton foo(){
console.log(a) // undefined
return
var a = 100
}
foo()
// 5
function foo(){
var a = b = 100
}
foo()
console.log(a) // c is not defined
console.log(b) // 100 , 相当于 var a = 100; b = 100
内存管理
内存的生命周期
- 分配申请你所需的内存
- 使用分配的内存(存放变量,对象等)
- 不需要使用时,对其进行释放
JS内存分配
- JS对原始数据类型的内存分配直接在栈空间进行分配
- JS对复杂数据类型内存的分配会在堆内存中开辟空间,将空间指针地址返回给变量引用
垃圾回收机制 GC(Garbage Collection)
对于那些不再使用的对象,称之为垃圾,需要被回收。Lisp最先提出
引用计数 (Reference counting)
-
概念
- 一个对象有一个引用指向它时,对象的引用就+1
- 当一个对象的引用为0时,这个对象就可以被销毁
-
缺陷
-
循环引用
obj1 = {} obj2 = {} obj1.info = obj2 obj2.info = obj1
-
标记清除 (mark-Sweep)
- 概念
- 核心思路:可达性
- 设置一个根对象,定期从根对象开始,找所有从根开始有引用到的对象,对于那些没有引用到的对象,就认为是不可用的对象
- 可以很好的解决循环引用的问题
标记整理
- 与标记清除类似
- 不同的是,回收期间将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化
分代回收
- 将内存中对象分为新生代、老生代
- 新创建的对象,都放到新生代,使用结束以后,GC清除垃圾,经过多次回收,还剩下的对象放到老生代中
- 老生代的检查频率是很低的。
增量收集
- 如果有很多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
- 所以引擎试图将垃圾收集工作分为几部分来做,然后将这几部分逐一处理,这样会有许多微小的延迟而不是一个很大的延迟
闲时收集
- GC只会在CPU空闲时尝试运行,以减少可能对代码执行的影响
闭包
- 最早出现在Scheme
- 如果一个函数,能够访问外层作用域中的变量,那么这个函数和周围环境就是一个闭包
- 所以,JS中的函数都可以称为闭包函数,因为每当创建一个函数时,这个函数都可以访问外层,如GO中的变量
function createAdder(con){
function adder(num){
return con+num
}
return adder
}
var adder5 = createAdder(5) // con固定为5 addr函数中num+5
adder5(10) // 15 5+10
var adder10 = createAdder(10) // con固定为10 addr函数中num+5
adder10(10) // 20 10+10
内存泄漏与释放
- 对于某些内存不再使用,需要手动进行释放
add10 = null
内存优化
- AO不使用的属性会被浏览器优化
函数增强
对象属性
// 对象属性
function foo(){}
foo.mes = 'xxx'
console.log(foo.mes) // xxx
// 默认属性,name/length(参数的个数)/arguments
// 剩余参数,length 不会将剩余参数算在内,默认值也不会算在内
function foo(...arg){console.log(arg)} // [1,2,3]
foo(1,2,3)
arguments
-
类数组对象
/* 可以使用length,索引 不可以使用map,filter等 转换成数组 1. for of push 2. [...arguments] 3. Arrary.from(arguments) 4. slice(start,end) [].slice.apply(arguments) 5. Array.prototype.slice.apply(arguments) */
-
在箭头函数中是不会绑定
arguments
的,会一层一层沿着作用域寻找arguments
纯函数
满足以下条件,被称为纯函数:
- 此函数在相同输入的情况下,必须是相同的输出
- 函数的输出与输出值以外的其他隐藏信息和状态无关
- 不能在语义上可观察的函数副作用,如“触发事件”
- 除了返回函数值外,还进行了一些其他操作,如修改全局变量等
splice和slice
splice 不是一个纯函数 修改原来数组
slice 是一个纯函数 不会修改原来数组,返回一个新的数组
优势
- 安心编写、安心使用
- 编写的时候不需要关注外部变量状态,只关注自己的业务逻辑即可
- 使用的时,不会修改外部资源
柯里化 Curring
概念:将一个函数转换成一个接收参数的,并且返回一个函数去处理剩余参数的过程称为柯里化
// 普通函数
function foo(a,b,c) {
console.log(a,b,c)
}
foo(1,2,3)
// 柯里化
function bar(a){
return function(b){
return function(c){
console.log(a,b,c)
}
}
}
bar(1)(2)(3)
// 柯里化另一种写法:箭头函数
var foo2 = a => b => c => console.log(a,b,c)
foo2(1)(2)(3)
自动柯里化封装
function foo(a,b,c){
console.log(a,b,c)
}
function hyCurrying(fn){
function currying(...arg){
if (arg.length>=fn.length) {
// 传入参数如果等于原函数的形参个数
return fn(...arg) // 可以绑定this fn.apply(this,arge)
} else {
// 如果传入函数小于形参个数,返回一个函数去处理剩余参数
return function(...newArr){
return currying(...[...arg,...newArr]) // 可以绑定currying.apply(this,[...arg,...newArr])
}
}
}
return currying
}
var curryingFn = hyCurrying(foo) // 柯里化
curryingFn(1,2,3) // 1 2 3
curryingFn(1)(2,3) // 1 2 3
curryingFn(1)(2)(3) // 1 2 3
应用场景
// 应用一:参数复用
function uri_curring(protocol) {
return function(hostname, pathname) {
return `${protocol}${hostname}${pathname}`;
}
}
// 测试一下
const uri_https = uri_curring('https://');
const uri1 = uri_https('www.fedbook.cn', '/frontend-languages/javascript/function-currying/');
const uri2 = uri_https('www.fedbook.cn', '/handwritten/javascript/10-实现bind方法/');
const uri3 = uri_https('www.wenyuanblog.com', '/');
console.log(uri1);
console.log(uri2);
console.log(uri3);
// 应用二:兼容性检测 每次写监听事件的时候调用 addEvent 函数,都会进行 if...else... 的兼容性判断
// 使用立即执行函数,当我们把这个函数放在文件的头部,就可以先进行执行判断
const addEvent = (function() {
if(window.addEventListener) {
console.log('判断为其它浏览器')
return function(element, type, listener, useCapture) {
element.addEventListener(type, function(e) {
listener.call(element, e);
}, useCapture);
}
} else if(window.attachEvent) {
console.log('判断为 IE9 以下浏览器')
return function(element, type, handler) {
element.attachEvent('on'+type, function(e) {
handler.call(element, e);
});
}
}
}) ();
// 提前返回
// 测试一下
let div = document.querySelector('div');
let p = document.querySelector('p');
let span = document.querySelector('span');
addEvent(div, 'click', (e) => {console.log('点击了 div');}, true); // 和延迟执行,返回的是一个函数
addEvent(p, 'click', (e) => {console.log('点击了 p');}, true);
addEvent(span, 'click', (e) => {console.log('点击了 span');}, true);
组合函数
// 传入多个函数,自动组合在一起,一起调用
function composeFn(...fns){
var length = fns.length
if (fns.length <=0 ) return
// 边界判断
for (var fn of fns) {
if (typeof fn !== 'function'){
throw new Error('fn must be function')
}
}
return function(...args){
var result = fns[0].apply(this,args)
for (var i = 1; i< length; i++) {
var fn = fns[i]
result = fn.apply(this,[result])
}
}
}
var newFN = composeFn(fn1,fn2,...)
with:不推荐使用
var obj ={
msg : 'hello'
}
msg = 'aa'
with (obj) {
console.log(msg) // hello
}
eval
var evalStr = 'var name="hyf";console.log(name)'
eval(evalStr)
- eval代码的可读性非常的差
- eval是一个字符串,那么有可能在执行的过程中被篡改,造成被攻击的风险
- eval的执行必须经过JavaScript解释器,不能被JavaScript引擎优化
严格模式
"use strict"
/*
1. 不会意外创建全局变量
2. 发现静默错误 Object.defineProperty(obj,'name',{writable:false})
3. 不允许0的八进制语法
4. 不允许使用with
5. eval不再为上层创建变量
6. this不会转化成对象类型 undefined
*/
对象增强
对象属性的控制
/*
属性描述符
Object.defineProperty(obj,prop,descriptor)
*/
属性描述符分类
-
数据属性描述符
-
[[configurable]]: 表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符
-
当我们直接在一个对象上定义某个属性时,这个属性的的[[configurable]]为true
-
当我们通过属性描述符定义一个属性时,这个属性的[[configurable]]默认为false
var obj = { name:'why', [[configurable]]:true age:18 } Object.defineProperty(obj,'addr',{}) // addr的[[configurable]]:false
-
-
[[Enumberable]]: 表示属性是否可以通过for-in或者Object.keys()返回属性值
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Enumerable]]为true
- 当我们通过属性描述符定义一个属性时,这个属性的[[Enumberable]]默认为false
-
[[Writable]]: 是否可以修改属性的值(只读属性)
- 当我们直接在一个对象上定义某个属性时,这个属性的[[Writable]]为true
- 当我们通过属性描述符定义一个属性时,这个属性的[[Writable]]默认为false
-
[[value]]: 告诉js引擎,返回这个value
-
-
存取属性描述符
-
[[configurable]]
-
[[Enumberable]]
-
get: 取值时
-
set:修改值时
var obj = { name:'why', age:18 } var _name = '' Object.defineProperty(obj,'name',{ configurable:true, set:function(value){ _name = value console.log('set:',_name) }, get:function(){ console.log('get:',_name) return _name } }) obj.name = 33 console.log(obj.name)
-
多个属性描述符
var obj = {
name:'why',
age:18
}
Object.defineProperty(obj,{
name:{
configurable:false
},
age:{
configurable:false
}
})
对象方法
getOwnPropertyDescriptor
:获取属性描述符getOwnPropertyDescriptors
:获取多个属性描述符preventExtensions
: 阻止对象扩展新属性seal
:密封对象,不能配置和删除属性freeze
:冻结对象,不能写入
对象方法
hasOwnProperty
:属性是否属于对象本身in/for in操作符
:遍历的是自己对象和原型上的属性instanceof
:判断对象与构造函数之间关系的,用于判断构造函数的prototype,是否出现在某个实例对象的原型链上isPrototypeOf
:判断对象与对象之间的关系的
原型
ES5-普通对象的原型
每一个对象都有一个[[prototype]]:object,称为对象的原型对象,
原型的作用:当通过[[get]]获取属性值时,先在自己的对象中查找,如果不存在,则会在原型对象中查找,原型对象中没有找到,还会在原型对象的原型对象中查找...直到null
所有函数都有一个prototype属性
-
对象:非标准
obj.__proto__
,标准Object.getPrototypeOf(obj)
-
函数
- 作为对象:非标准
foo.__proto__
,标准Object.getPrototypeOf(foo)
- 作为函数:
foo.prototype
,显式原型,用来构建对象时,给对象设置隐式原型
- 作为对象:非标准
-
函数的原型作用:在通过new操作符创建对象时,将这个显式原型赋值给创建出来的对象的隐式原型,避免实例化时创建多个函数
// 将方法设置到原型上,实例化对象时,方法函数将不会大量创建,方法函数只有一份 function Foo(name,age){ this.name = name this.age = age /*eating:function(){ console.log(this.name+'eating') }*/ } Foo.prototype.eating = function(){ console.log(this.name+'eating') } var name1 = new Foo('hyf',18) var name2 = new Foo('hyf01',18) name1.eating() // hyfeating /* , eating函数中的this指向name1,因为,name1.__proto__ === Foo.prototype, [eating]先在 name1中找,没有找到,在原型对象name1.__proto__即Foo.prototype中寻找 name1.eating()中的this,指向调用者本身,即name1 */ name2.eating() // hyf01eating
显示原型的属性
function Person(){
}
Person.prototype.running = function(){}
Person.prototype.msg = 'hello'
// constructor:指向函数
console.log(Person.prototype) // {msg: 'hello', running: ƒ, constructor: ƒ}
Person.prototype.constructor === Person // true
Person.name = "Person" // true
Person.prototype.constructor.name = "Person" // true
原型重写
// 如果原型对象属性很多,可以进行原型对象重写
Person.prototype = {
...
// 默认没有constructor,最好在defineProperty中定义
// constructor:Person
}
// 更加精准的控制
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
configurable:true,
writable:false,
value:Person
})
原型链
var obj ={}
属性的查找顺序是: obj--->obj.__proto__----> obj.__proto__._proto__ ----->null
Object是所有类的父类
面向对象
三大特征
-
封装:属性和方法封装到一个类中,称为封装的过程
-
继承:将类中共同部分提取出来,作为一个单独的类,称为父类,子类类继承父类的公用属性或方法
// 继承方式一:父类的原型直接赋值给子类的原型,缺点:父类与子类共享一个原型对象,修改任何一个,另一个也被修改 // 继承方式二:创建一个父类的实例,用这个实例对象作为子类的原型对象,缺点:子类在独立属性和代码复用上有分歧 // 继承方式三(最终方案):借用构造函数,组合继承(借用构造函数+原型链) function Person(props){ this.props = props } var p = new Person('dd') // 第一次调用 function Student(props,prop1,prop2){ Person.call(this,props) // 第二次调用 // this.props = props this.prop1 = prop1 this.prop2 = prop2 }
-
多态:不同的数据类型进行同一个操作时,表现出不同的形态,称为多态的表现
-
必须有继承
-
必须有父类引用指向子类对象
-
对于JS来说,处处都是多态
-
组合继承的缺陷
-
调用两次父类构造函数:new 、 call
-
所有子类实例实际上会有两份父类的属性,Stul1中有一份,p对象中也有一份
寄生组合继承:
原型链+借用+原型式继承+寄生式函数
/*
满足什么条件:
1. 必须创建一个对象
2. 这个对象的隐式原型必须指向父类的显式原型
3. 将这个对象赋值给子类的显式原型
*/
// 原型式继承
function createObject(o){ // 出现兼容问题时将Object.create替换成createObject
function F(){}
F.prototype = o
return new F()
}
// 将Subtype依赖Supertype,称为寄生式函数
function inherit(Subtype,Supertype){
Subtype.prototype = Object.create(Supertype.prototype) // 原型链
Object.defineProperty(Subtype,'constructor',{
configurable:true,
writable:true,
enumerable:false,
value:Subtype
})
// 类方法的继承
Object.setPrototypeOf(Subtype,Supertype)
}
function Person(){}
function Student(){}
inherit(Student,Person)
最终方案
function Person(name,age,height){
this.name = name
this.age = age
this.height = height
}
Person.prototype.running = function(){
console.log('running')
}
Person.prototype.eating = function(){
console.log('eating')
}
function Student(name,age,height,sno,score){
// this.name = name
// this.age = age
// this.height = height
Person.call(this,name,age,height) // 继承Person中的属性 借用
this.sno = sno
this.score = score
}
// 使用寄生式函数
inherit(Student,Person)
Student.prototype.studying = function(){
console.log('studying')
}
Object类是所有类的父类
ES5-构造函数的类方法
function Person(name,age){
this.name = name
this.age = age
}
// 实例方法
Person.prototype.running = function(){
console.log('running')
}
// 函数对象
Person.eating = function(){
console.log('eating')
}
Person.eating()
ES6
类
class Person {
// 当通过new关键字调用一个类时,默认调用class中的constructor方法
constructor(name,age){
this.name = name
this.age = age
}
running(){ // 内聚 Person.prototype.running = function(){ console.log('running')}
console.log('running')
}
eating(){
console.log('eating')
}
}
var p1 = new Person('hyf01',17)
var p2 = new Person('hyf02',17)
console.log(Person.prototype === p1.__proto__)
// 另一种写法 var Student = class {}, 不常用
class 定义访问器
// 监听访问器方式一:
Object.defineProperty(obj,'name',{
configurable:true,
enumberable:true,
set:function(val){
},
get:function(){
}
})
// 方式二:
var obj = {
_name:'',
set name(){
this._name = value
},
get name(){
return this._name
}
}
// 类中 下划线属性一般不访问,程序员之间的约定,不是规范约定
class Person(){
constructor(name,age){
this._name = name
this._age = age
}
set name(val){
this._name = value
}
get name(){
return this._name
}
}
var p1 = new Person()
console.log(p1.name)
应用场景
class Rectangle {
constructor(x,y,width,height){
this.x = x
this.y = y
this.width = width
this.height = height
}
get position(){
return {x:this.x,y:this.y}
}
get size(){
return {width:this.width,height:this.height}
}
}
var r1 = new Rectangle(1,2,3,4)
console.log(r1.position) // {x:1,y:2}
定义静态方法
class Person {
constructor(){
}
eating(){ // 实例方法
}
static random(){ // 类方法,静态方法
}
}
继承 extends
// js只支持单继承
class Animal {
constructor(name,age){
this.name = name
this.age = age
}
eating(){
console.log(this.name+'is eating')
}
}
class Dog extends Animal{
constructor(name,age,home){
super(name,age) // super.method() 调用父类方法,super(xx) 调用constructor(),
// 使用this之前必须先调用super
this.home = home
}
running(){
console.log(this.name+'is running')
}
}
super用法
- 在子类的constructor中调用时必须在this.xx之前
- super(args),调用的是constructor()
- super.method(), 调用父类的方法
继承内置类
class MyArray extends Array {}
类的混入mixin
function mixinRunner(BaseClass){
// 返回一个类
return class extends BaseClass {
running(){
console.log('running')
}
}
}
class Dog{
eating(){
console.log('eating')
}
}
var NewDog = mixinRunner(Dog)
// 或者 class NewDog extends mixinRuuner(Dog) {}
var dog1 = new NewDog()
dog1.running()
dog1.eating()
对象字面量增强
var name = 'hyf'
var age = 18
var obj = {
name,
age,
running:function(){
},
clear(){ // 简写
},
eating:()=>{ // 箭头函数
},
// 计算属性名
[name+age]:3
}
数组和对象的解构赋值
var nums = [1,2,3,4,5,6,7]
var obj = {name:'hyf',age:18}
var [a,b,c,d,e,,g,f=8] = nums
var {name,age} = obj
var {name:newName,age:newAge} = obj
console.log(newName,newAge,name,age)
// 对象默认值
var {name:newName,age:newAge,bb='dd',...newObj} = obj
防抖和节流
underscore库
- 防抖:不断延迟执行
- 节流:一定时间内执行一次
网络请求
SSR和前后端分离
- SSR(Server side render): 服务端渲染
- 服务端返回整个页面给浏览器渲染
- 带宽增加
- ajax:动态替换页面中展示的数据,无需页面刷新
- 前后端分离
+
http
超文本传输协议:HyperText Transfer Protocol,应用层协议
组成
- 请求
- 请求行:方法 URI 协议版本
- 请求头:
- content-type:请求携带的数据类型
- application/x-www-form-urlencoded: 表示数据被编码成以'&'分隔的键-值对,同时以’=‘分隔键和值
- application/json: 表示一个json类型
- text/plain: 表示文本类型
- application/xml: 表示xml类型
- mutipart/form-data: 表示上传文件
- content-length:文件的大小长度
- keep-alive: 保持连接
- http1.0中,服务端和客户端都需要设置connection:keep-alive
- http1.1中默认connection:keep-alive,Node中默认是5s
- accept-encoding: 告知服务器,客户端支持的文件压缩格式,如gzip编码,对应.gz文件
- accept: 告知服务器,客户端可接收文件的格式类型
- user-agent: 客户端相关的信息
- content-type:请求携带的数据类型
- 请求体
- 响应
- 响应行:协议版本 状态码 状态码短语
- 常见状态码:
- 200: 客户端请求成功
- 201: POST请求,创建新的资源
- 301: 重定向,请求资源的URL已经修改,响应中会给出新的URL
- 400: 客户端错误,服务器无法或者不进行处理
- 401: 未授权错误,必须携带请求的身份信息
- 403: 客户端没有权限访问,被拒接
- 404: 服务器找不到请求的资源
- 500:服务器遇到了不知道如何处理的情况
- 503:服务器不可用,可能处于维护或者重载状态,暂时无法访问
- 常见状态码:
- 响应头:
- 响应体:
- 响应行:协议版本 状态码 状态码短语
版本
- http/0.9:1991,只支持get请求
- http/1.0: 1996 ,支持POST、HEAD等请求,不再局限文本数据,但每次都会建立一个TCP连接
- http/1.1: 1997, 增加PUT、DELETE等请求方法,采用持久连接(Connetion: keep-alive),多个请求可以共用一个TCP连接
- http/2.0: 2015
- http/3.0: 2018
请求方式
- get:用于获取数据
- head:与get响应相同,但是没有响应体,一般在下载文件前,回去文件的大小
- post:将实体提交给指定资源
- put:用请求有效载荷替换目标资源的所有当前表示
- delete:删除指定资源
- patch:用于对资源修改
- connect:通常用在代理服务器
- trace:沿着到目标资源的路径执行一个消息环回测试
AJAX发送请求
// 1. 创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()
// 2. 监听状态的改变
xhr.onreadystatechange = function(){
if (xhr.readyState !== XMLHttpRequest.DONE) return
const resJSON= JSON.parse(xhr.response)
console.log(resJSON)
}
// 告知xhr返回数据的类型
xhr.responseType = 'json'
// 3. 配置请求open/ 默认异步true
xht.open('POST',"http://xxxx",true)
// 4. 发送请求
xht.send()
事件监听on
- readystatechange
- loadstart: 请求开始
- progress: 一个响应数据包到达,此时整个response body都在response中
- abort: 调用xhr.abort()取消请求
- error: 发生连接错误,例如: 域错误
- load: 请求成功完成
- timeout: 由于请求超时而取消了该请求(仅发生在设置了timeout的情况下)
- loadend: 在load,error,timeout或者abort之后触发
state状态
响应状态status
- status
- statusText
GET/POST请求传递参数
- get: query
?name=ss&age=11
- post:
xhr.setRequestHeader('Content-Type',xxx)
- x-www-form-urlendcoded: 放到请求体中;xhr.send('name=ss&age=11')`
- formdata:
new FormData(formEl)
- json:
xhr.send(JSON.stringify(object))
Fetch
// fetch-get请求
fetch('xxx').then(res=>{
return res.json() // 返回一个Promise
}).then(res=>{
console.log(res)
}).catch(err=>{
console.log(err)
})
// post json
async function getInfo(){
const response = await fetch('xxxx',{
method:'post',
headers:{
'Content-Type':'application/json'
},
body:JSON.stringify(data)
})
const res = response.json()
console.log(res)
}
// post x-www-form-urlencoded
async function getInfo(){
const response = await fetch('xxxx',{
method:'post',
headers:{
'Content-Type':'application/x-www-form-urlencoded'
},
body:'name=hh&age=11'
})
const res = response.json()
console.log(res)
}
// formdata
async function getInfo(){
const formData = new FormData(el)
formData.append('xx',3)
const response = await fetch('xxxx',{
method:'post',
body:formData
})
const res = response.json()
console.log(res)
}