typeof能判断哪些类型
undefined string number boolean symbol funciton
不能识别的一律是object
typeof console.log // function
typeof function(){} // function
// 能识别应用类型(不能再继续识别)
typeof null // object
typeof ['a','b'] // object
typeof {x:100} // object
深拷贝手写
function deepClone(obj = {}) {
if (typeof obj !== "object" || typeof obj == null) {
// obj是null,或者不是对象和数组直接返回
return obj;
}
// 初始化返回结果
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
字符串拼接
const a = 100+10
const b = 100 + '10'
const c = true + '10'
==运算符
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
!!
// 以下是falsely变量,除此之外都是truly变量
!! 0 === false
!! NaN === false
!! '' === false
!! null === false
!! undefined === false
!! false === false
原型和原型链
class
- constructor
- 属性
- 方法
如何准确判断一个变量是数组
- 使用instanceof(原型链)
class的原型本质
- 原型和原型链的图示
- 属性和方法的执行规则
instanceof
类型判断
xiahu instanceof Student // true
xiahu instanceof People // true
xiahu instanceof Object // true
[] instanceof Array // true
[ ] instanceof Object // true
{} instanceof Object // true
原型
隐式原型和显示原型
console.log(xialuo.__proto__) //person
console.log(Student.prototype) //person
console.log(xialuo.__proto__ === Student.prototype) //person
基于原型的执行规则
1、获取属性xialuo.name 或者执行方法xialuo.sayhi()
2、先在自身属性和方法寻找
3、如果找不到则自动去__proto__中查找
原型关系
每个class都有显示原型prototype
每个实例都有隐式原型__proto__
实例的__proto指向对应的class的prototype
手写jquery考虑插件和扩展性
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector);
console.log("result", result);
const length = result.length;
for (let i = 0; i < length; i++) {
this[i] = result[i];
}
this.length = length;
}
get(index) {
return this[index];
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
fn(element);
}
}
on(type, fn) {
return this.each((element) => {
element.addEventListener(type, fn, false);
});
}
}
// 插件
jQuery.prototype.dialog = function () {};
// "造轮子"
class myJquery extends jQuery {
constructor(selector) {
super(selector);
}
// ...jQuery. 扩展自己的方法
}
- 插件也就是只原型上进行扩展
- 造轮子也就是扩展就是对他的继承
闭包
定义和总结
- 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
- 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;
闭包的内存泄露
- 那么我们为什么经常会说闭包是有内存泄露的呢?
1、在上面的案例中,如果后续我们不再使用add10函数了,那么该函数对象应该要被销毁掉,并且其引用着的父作用域AO也应该被销毁掉; 2、但是目前因为在全局作用域下add10变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以最终会造成这些内存都是无法被释放的; 3、所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的;
- 那么,怎么解决这个问题呢?
1、因为当将设置为null时,就不再对函数对象0xb00有引用,那么对应的AO对象0x200也就不可达了; 2、在GC的下一次检测中,它们就会被销毁掉;
作用域
- 全局作用域
- 函数作用域
- 块级作用域
this的不同应用场景
- 当做函数被调用
- 使用call apply bind
- 作为对象方法调用
- 在class的方法中调用
- 箭头函数
手写bind函数
function fn1(a, b, c) {
console.log("this", this);
console.log(a, b, c);
return "this is fn1";
}
// call() 和 apply() 之间的区别
// 不同之处是:
// call() 方法分别接受参数。
// apply() 方法接受数组形式的参数。
// 如果要使用数组而不是参数列表,则 apply() 方法非常方便。
const fn2 = fn1.bind({ x: 100 }, 100, 200);
const res = fn2;
// fn1.__proto__ === Function.prototype // true
// 由此可得
// 模拟bind
Function.prototype.bind1 = function () {
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
// 将参数解析为数组
const args = Array.prototype.slice.call(arguments);
// 获取 this(数组第一项)也就是需要绑定的this
const t = args.shift();
// 之前的this 先放一个变量里去
const self = this;
return function () {
return self.apply(t, args);
};
};
从js基础知识到JS web api
- JS基础知识,规定语法(ECMA 262标准)
- JS Web API,网页操作的API(w3c标准)
- 前者是后者的基础,两者集合才能真正实际应用
JS WEB API
- DOM
- BOM
- 事件绑定
- ajax
- 存储
DOM
DOM本质
DOM本质就是一棵树
DOM节点的property
const pList = document.querySelectorAll("p")
const p = pList[0]
console.log(p.style.width)
p.style.width = '100px'
p.className = 'p1'
console.log(p.nodeName)
console.log(p.nodeType)
//property 所以不是api的名字他是一种形式
DOM节点的attribute
const pList = document.querySelectorAll("p")
pList.getAttribute("className")
pList.setAttribute("data-name","zx")
//
property和attribute
- property:修改对象属性,不会体现到html结构中
- attribute:修改html属性,会改变html结构
- 两者都有可能引起dom重新渲染
DOM性能
- DOM操作非常“昂贵”避免频繁的DOM操作
- 对DOM查询做缓存
- 将频繁操作改为一次操作
事件代理
- 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用(比如明明就一个点击事件还要去事件代理)
描述下事件冒泡的流程
- 基于DOM树形结构
- 事件会顺着触发元素向上冒泡
- 应用场景:代理
无线下拉图片列表,如何监听每个图片的点击
- 事件代理
- 用e.target获取触发元素
- 用matches来判断是否是触发元素的
function bindEvent(elem, type, selector, fn) { if (fn == null) { fn = selector selector = null } elem.addEventListener(type, event => { const target = event.target if (selector) { // 代理绑定 if (target.matches(selector)) { fn.call(target, event) } } else { // 普通绑定 fn.call(target, event) } }) } // 普通绑定 const btn1 = document.getElementById('btn1') bindEvent(btn1, 'click', function (event) { // console.log(event.target) // 获取触发的元素 event.preventDefault() // 阻止默认行为 alert(this.innerHTML) }) // 代理绑定 const div3 = document.getElementById('div3') bindEvent(div3, 'click', 'a', function (event) { event.preventDefault() alert(this.innerHTML) }) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>事件 演示</title> <style> div { border: 1px solid #ccc; margin: 10px 0; padding: 0 10px; } </style> </head> <body> <button id="btn1">一个按钮</button> <!-- <div id="div1"> <p id="p1">激活</p> <p id="p2">取消</p> <p id="p3">取消</p> <p id="p4">取消</p> </div> <div id="div2"> <p id="p5">取消</p> <p id="p6">取消</p> </div> --> <div id="div3"> <a href="#">a1</a><br> <a href="#">a2</a><br> <a href="#">a3</a><br> <a href="#">a4</a><br> <button>加载更多...</button> </div> <script src="./event.js"></script> </body> </html>
ajax
XMLHttpRequest
// get 请求 const xhr = new XMLHttpRequest(); xhr.open("GET", "/api", false); xhr.onreadystatechange = function () { // 这里的函数异步执行,可参考之前的js基础中的异步模块 if (xhr.readyState == 4) { if (xhr.status == 200) { console.log(xhr.responseText); } } }; xhr.send(null); const xhr = new XMLHttpRequest(); xhr.open("POST", "./data.json", false); xhr.onreadystatechange = function () { // 这里的函数异步执行,可参考之前的js基础中的异步模块 if (xhr.readyState == 4) { if (xhr.status == 200) { console.log(xhr.responseText); } else { console.log(xhr.responseText); } } }; const postData = { name: "zx", password: "xxx", }; xhr.send(JSON.stringify(postData));
xhr.readyState
- 0 - unset 尚未调用open方法
- 1 - opened open 方法已被调用
- 2 - HEADERS_RECEIVED send 方法已被调用,header已被接收
- 3 - loading下载中,responseText已有部分内容
- 4 - done 下载完成
JSONP和cors
JSONP的实现主要是
<script></script><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>jsonp 演示</title> </head> <body> <p>一段文字 1</p> <script> window.abc = function (data) { console.log(data); }; </script> <script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script> </body> </html> abc( { name: 'xxx' } )
存储
描述cookie localStorage sessionStorage区别
cookie
本身用于浏览器和server通讯
被“借用”到本地存储来
可用document.cookie来修改
缺点
- 存储大小 最大4kb
- http请求时需要发送到服务端,增加请求数据量
- 只能用document.cookie="..."来修改,太简陋了
localStorage和sessionStorage
h5专门为存储设计的,最大可存5m
API简单易用 setItem getItem
不会随着http请求被发送出去
区别
- localStorage数据会永久存储,除非你代码或者手动删除
- sessionStorage 数据只存在于当前会话,浏览器关闭则清空
BOM
navigator 和 screen
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
screen.width
screen.height
location和history
https://www.bilibili.com/video/BV1W24y1b7n4/?spm_id_from=333.1007.tianma.2-1-4.click&vd_source=462b5b0d167594bb2782aad6421bba87ocation.href // 'www.bilibili.com/'
location.protocol // 'https:'
location.hostname // 'www.bilibili.com'
location.pathname // '/video/BV1W24y1b7n4/'
location.search // '?spm_id_from=333.1007.tianma.2-1-4.click&vd_source=462b5b0d167594bb2782aad6421bba87'
history.back
history.forward
H5抓包
- 移动端h5页,查看网络请求,需要用工具抓包
- windows一般用fiddler
- masc os一般用charles
webpack和babel
ES6模块化,浏览器不支持
ES6语法,浏览器并不完全支持
压缩代码,整合代码,以让网页加载更快
webpack搭建
npm init npm i webpack webpack-cli -D npm i html-webpack-plugin -D npm i webpack-dev-server -D配置babel
1、npm install @babel/core @babel/preset-env babel-loader -D 2、创建 .babelrc { "presets": ["@babel/preset-env"] } 3、配置webpack.config,js的module { test: /.js$/, loader: ["babel-loader"], include: path.join(__dirname, "src"), exclude: /node-modules/, },如何配置webpack生产环境
const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "production", // development是没有压缩的 production是压缩的 entry: "./src/index.js", output: { path: path.resolve(__dirname, "./dist"), filename: "bundle.[contenthash].js", }, resolve: { extensions: [".ts", ".js"], }, module: { rules: [ { test: /.js$/, loader: "babel-loader", include: path.join(__dirname, "src"), exclude: /node_modules/, }, { test: /.ts$/, loader: "ts-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ template: "./index.html", // filename: "index.html", }), ], };
网页url加载
- DNS解析:域名 -> IP地址
- 浏览器根据IP地址向服务器发起http请求
- 服务器处理Http请求,并返回给浏览器
网页渲染过程
- 根据HTML代码生成 DOM tree
- 根据css代码生成CSSOM
- 将DOM Tree 和CSSOM整合形成Render Tree
- 根据Render tress 渲染页面
- 遇到
<script>则暂停渲染,优先加载并执行js代码,完成在继续(js和渲染是共享同一个进程的,因为js可能会改变DOM)- 直至把RenderTree渲染完成
css为什么放到header中
首先整个页面展示给用户会经过
html的解析与渲染过程。如果将css放在尾部,html的内容可以第一时间显示出来,但是会阻塞html行内css的渲染。浏览器的这个策略其实很明智的,想象一下,如果没有这个策略,页面首先会呈现出一个行内
css样式,待CSS下载完之后又突然变了一个模样。用户体验可谓极差,而且渲染是有成本的。如果将
css放在头部,css的下载解析是可以和html的解析同步进行的,放到尾部,要花费额外时间来解析CSS,并且浏览器会先渲染出一个没有样式的页面,等CSS加载完后会再渲染成一个有样式的页面,页面会出现明显的闪动的现象。
JS为什么要放到body之后
js本身是会阻塞渲染,js和渲染是共享同一个进程的,因为js可能会改变DOM,等把该渲染出来的渲染,再去加载js
window.onload和DOMContentLoaded
window.addEventListener('load',function(){
// 页面的全部资源加载完成才会执行,包括图片、资源等
})
document.addEventListener('DOMContentLoaded',function(){
// DOM 渲染完成即可执行,此时图片,视频还没可能没有加载完
})
性能优化
多使用内存,缓存或其他的方法
减少cpu计算量,减少网络加载耗时
适用于所有编程的性能优化------ 空间换时间
让加载更快
- 减少资源体积:压缩代码
- webpack:打包方式:production
资源合并(多次变少次请求)
缓存 ---> webpack
静态资源加hash后缀,根据文件内容计算hash
文件内容不变,则hash不变,则url不变
- url和文件不变,则会自动触发http触发缓存机制,返回304
ssr
- 服务端渲染:将网页和数据一起加载,一起渲染
- 非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
- 早先的jsp asp php现在的vue react
懒加载(图片)
缓存DOM查询
- 能一次性DOM就一次性不要频繁去操作DOM
尽早的开始js执行
- window.onload和DOMContentLoaded使用第二者
防抖
- 监听一个输入框的,文字变化后触发change事件
- 直接用keyup事件,则会频繁触发change事件
- 防抖:用户输入结束或暂停时,才会触发change事件
const input1 = document.getElementById("input1"); // let timer = null; // input1.addEventListener("keyup", function () { // if (timer) { // clearTimeout(timer); // } // timer = setTimeout(() => { // console.log("[Log] input1-->", input1.value); // // 清空定时器 // timer = null; // }, 500); // }); function debounce(fn, delay = 500) { // timer是闭包中的 let timer = null; return function () { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, arguments); // 清空定时器 timer = null; }, delay); }; } input1.addEventListener( "keyup", debounce(() => { console.log("[Log] input1-->", input1.value); }) ); 节流
const div1 = document.getElementById("div1"); let timer = null; // div1.addEventListener("drag", function (e) { // if (timer) { // return; // } // timer = setTimeout(() => { // console.log(e.offsetX, e.offsetY); // timer = null; // }, 100); // }); function throttle(fn, delay = 500) { let timer = null; return function () { if (timer) { return; } timer = setTimeout(() => { fn.apply(this, arguments); }, delay); }; } div1.addEventListener( "drag", throttle(() => { console.log(e.offsetX, e.offsetY); }) );
安全
xss跨站请求攻击
嵌入
<script>脚本 获取cookie发送到我的服务器预防
- 替换特殊字符如
<变为<- 前端后端都替换 都做
xsrf跨站请求伪造
你正在购物,看中了商品商品id是100
付费接口xxx.com/pay?id=100,但没有任何验证
我是攻击者我看中了一个商品id是200
发一封邮件,邮件隐藏着<img src=xxx.com/pay?id=200>
你查看邮件,就帮我购买id是200的商品
预防
- 使用post接口
- 增加验证:例如密码,短信验证指纹等等