常见的值类型
- undefined
- string
- number
- boolean
- Symbol
常见引用类型
- 对象
- 数组
- null 特殊引用类型,指针指向空地址
- 函数
typeof运算符
- 识别所有值类型
- 识别函数
- 判断是否是引用类型(不可在细分)
手写深拷贝
<script>
const obj1 = {
name: 'stone',
age: 20,
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1);
obj2.address.city = 'shanghai';
obj2.arr[0] = 'a1';
console.log(obj1.address.city);//beijing
console.log(obj2.address.city);//shanghai
console.log(obj1.arr[0]);//a
console.log(obj2.arr[0]);//a1
//深拷贝
function deepClone(obj = {}) {
if(typeof obj !== 'object' || obj == null) {
return obj;
}
// 初始化返回结构
let result;
if(obj instanceof Array) {
result = [];
}else {
result = {};
}
for(let key in obj) {
//保证key不是原型的属性
if(obj.hasOwnProperty(key)) {
//递归调用
result[key] = deepClone(obj[key]);
}
}
return result
}
</script>
原型
- 隐式原型 __proto__
- 显示原型 prototype
原型关系:
- 每个class都有显示原型prototype
- 每个实例都有隐式原型__proto__
- 实例的__proto__指向对应class的prototype
- instanceof 实则式通过原型链一层一层去寻找是否有这样的东西从而实现的
列如:判断一个一个变量是不是数组 a instanceof Arra
手写jquery
/** HTML内容
* <P>文字</P>
<P>文字</P>
<P>文字</P>
*/
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
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 elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
//扩展很多DOM API
}
//插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
//造轮子
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
//扩展自己的方法
addClass(className) {
}
style(data) {
}
}
//const $p = new jQuery('P')
//$P.get(1)
//$p.each((elem) => console.log(elem.nodeName))
//$p.on('click', () => alert('clicked'))
字面量new出来的对象和 Object.create(null)创建出来的对象有什么区别
- 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,
- 而
Object.create(null)创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性
作用域和闭包
什么是作用域,什么是作用域链?
-
规定变量和函数的可使用范围称作作用域
-
每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链
作用域:
- 全局作用域
- 函数作用域
- 块级作用域(ES6) 例如: if for while
自由变量
- 一个变量再当前作用域没有定义,但被使用了
- 向上级作用域(父级),一层一层依次寻找,知道找到为止
- 如果全局作用域中都没有找到,则报错 xx is not defined
什么是闭包?
函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用
作用:
保护:避免命名冲突
保存:解决循环绑定引发的索引问题
变量不会销毁:可以使用函数内部的变量,使变量不会被垃圾回收机制回收
闭包: 作用域应用的特殊情况,有两种表现: 1.函数作为参数被传递 2.函数作为返回值被返回
<script>
//函数作为返回值---------------------------------
function create() {
let a = 100
return function() {
console.log(a)
}
}
const fn = create();
const a = 200
fn()//100
//函数作为参数被传递---------------------------------------
function print(fn) {
const a = 200
fn()
}
const a = 100
function fn() {
console.log(a)
}
print(fn)//100
</script>
总结:闭包自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方查找
this
场景
作为普通函数被调用 返回window
使用 call apply bind 被调用 传入什么返回什么
作为对象方法被调用 返回对象本身
在class方法中调用 返回当前实例的这个本身
箭头函数中调用 永远会去找上级作用域的this来确定
this的不同应用场景,如何取值?
this取什么值是在函数执行的时候确定的 不是在函数确定的时候确定的
手写bind函数
<script>
function fn1(a, b, c) {
console.log('this:', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
//模拟bind
Function.prototype.bind1 = function() {
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
//获取this(数组第一项)
const t = args.shift()
//fn1.bind(...)中的fn1
const self = this
//返回一个函数
return function() {
return self.apply(t, args)
}
}
</script>
实际开发中闭包的应用:
隐藏数据 做一个简单的cache工具
<script>
//闭包隐藏数据,只提供API
function createCache() {
const data = {} //闭包中的数据,被隐藏,不被外界访问
return {
set: function(key, value) {
data[key] = value
},
get: function(key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log(c.get('a'))
</script>
单线程和异步
- JS是单线程语言,只能同时做一件事
- 浏览器和NODEJS已支持JS启动进程,如Web Worker
- JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
异步和同步 同步和异步的区别是什么?
- 基于JS是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
异步应用场景(等待情况)
- 网络请求,如AJAX图片加载
- 定时任务,如setTimeout
手写Promise 加载一张图片
<script>
const url = 'http://........................'
function loadingImg(src) {
return new Promise((resolve, reject) =>{
const img = document.createElement('img');
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src} `)
reject(err)
}
img.src = src
})
}
loadingImg(url).then(res => {
console.log(res.width)
return res
}).then(res => {
console.log(res.height)
}).catch(err => {
console.log(err)
})
</script>
DOM
文档对象模型 这样的一个集合 DOM操作(Document Object Model)
properrty和attribute
- property:修改对象属性,不会体现到HTML结构中
- attribute:修改html属性,改变html结构
- 两者都有可能引起DOM重新渲染
DOM性能
- DOM操作非常"昂贵",应避免频繁的DOM操作
- 对DOM查询做缓存
- 将频繁操作改为一次性操作
BOM操作 (Browser Object Model)
如何识别浏览器的类型
- navigator 识别浏览器的信息 简称UA
- screen 一般查看浏览器的宽高
- location 一般查看网址
- history 前进后退等
事件
事件绑定
事件冒泡
- 基于DOM树形结构
- 事件会顺着出发元素往上冒泡
- 应场场景:代理
事件代理(瀑布流)
- 代码简洁
- 减少浏览器内存占用
- 但是不要滥用
- 事件代理是基于事件冒泡的
Ajax
手写AJAX
<script>
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if(xhr.status === 200) {
resolve(
JSON.parse(xhr.responseText)
)
}else if(xhr === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null)
})
return p
}
const url = 'http://xxxx'
ajax(url).then(res => console.log(res)).catch(err => console.log(err))
</script>
跨域
什么是跨域(同源策略)
- ajax请求时,浏览器要求当前网页和server必须同源(安全)
- 同源:协议、域名、端口,三者必须一
跨域
- 所有的跨域,都必须经过server端允许和配合
- 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
JSONP
访问xxx.com/ ,服务端一定返回一个html文件吗?
服务器可以任意动态拼接数据返回,只要符合html格式要求
jQuery实现JSONP
<script>可绕过跨域限制
服务器可以任意动态拼接数据返回
所以,<script>就可以获得跨域的数据,只要服务端愿意返回 <img src=""/>等等
CORS(服务端支持)
纯服务端去做的
存储
描述cookie localStorage sessionStorage区别
cookie
- 本身用于浏览器和server通讯
- 被“借用”到本地存储来
- 可用document.cookie=“...”来修改
- 缺点:最大存储4KB。http请求时需要发送到服务端,增加请求数据量只能用document.cookie=“...”来修改,太过简陋
localStorage和sessionStorage(一般用loaclStorage会更多一些)
- HTML5专门为存储而设计,最大可存5M
- API简单易用setItem getItem
- 不会随着http请求被发送出去
localStorage数据会永远存储,除非代码或手动删除
sessionStorage数据只存在于当前会话,浏览器关闭则清空
防抖debounce
场景:
- 监听一个输入框,文字变化后触发change事件
- 直接用keyuo事件,则会频繁触发change事件
- 防抖:用户输入结束或暂停时,才会触发change事件
<body>
<input type="text" id="input1">
<script>
const input1 = document.getElementById("input1");
//--------------------案例---------------------------防止频繁触发 频繁请求有损性能
// let timer = null
// input1.addEventListener('keyup', function() {
// if(timer) {
// clearTimeout(timer)
// }
// timer = setTimeout(() => {
// console.log(input1.value) //模拟触发change事件
// //清空定时器
// 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(function() {
console.log(input1.value)
}), 600)
</script>
<body/>
节流throttle
- 拖拽一个元素时,要随时拿到该元素被拖拽的位置
- 直接用drag事件,则会频繁触发,很容易导致卡顿
- 节流:无论拖拽速度多块,都会每隔100ms触发一次
<body>
<div id="div1" draggable="true">可拖拽</div>
<script>
//--------------------案例---------------------------
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 = 100) {
let timer = null
return function() {
if(timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
div1.addEventListener('drag', throttle(function(e) {
console.log(e.offsetX, e.offsetY);
},200))
</script>
</body>
基础算法
冒泡算法排序
// 冒泡排序
/* 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2.第一轮的时候最后一个元素应该是最大的一个。
3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 */
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
bubbleSort(Arr)
console.log(Arr, "after");
快速排序
/*
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。
然后递归调用,在两边都实行快速排序。
*/
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
var middle = Math.floor(arr.length / 2)
var middleData = arr.splice(middle, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] < middleData) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([middleData], quickSort(right))
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
var newArr = quickSort(Arr)
console.log(newArr, "after");
插入排序
function insertSort(arr) {
// 默认第一个排好序了
for (var i = 1; i < arr.length; i++) {
// 如果后面的小于前面的直接把后面的插到前边正确的位置
if (arr[i] < arr[i - 1]) {
var el = arr[i]
arr[i] = arr[i - 1]
var j = i - 1
while (j >= 0 && arr[j] > el) {
arr[j+1] = arr[j]
j--
}
arr[j+1] = el
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
insertSort(Arr)
console.log(Arr, "after");