一、JS基础-变量类型和计算
1 JS 值类型和引用类型的区别
const n = null //特殊的引用类型,指针指向空地址
function fn(){} //特殊的引用类型,不用于存储数据,没有'拷贝、复制函数'一说
1、type of运算符
1.1识别所有的值类型 1.2识别函数
typeof console.log // 'function'
2、深拷贝
function deepClone(obj = {}){
if(typeof obj !=='object' || obj == null) return obj;
//初始化返回结果
let res
if(obj instanceof Array){
res = []
}else{
res = {}
}
for(let key in obj){
//确保 key 不是原型的属性
if(obj.hasOwnProperty(key)){
res[key] = deepClone(obj[key])
}
}
return res
}
3、变量计算-数据类型转换
(1)字符串拼接
(2)==运算符
//除了 == null,其余一律使用 ===
const obj = { x:100}
if(obj.a == null){}
//等价于
if(obj.a === null || obj.a === undefind){}
(3)if语句和逻辑运算
truily变量 !!a === true 的变量
falsely变量 !!a === false 的变量
例如: 0 NaN '' null indefined false
二、原型和原型链(超重要,必问!!!!)
1、如何准确判断一个变量是不是数组?
a instanceof Array
2、手写一个简易的jqurey:不会!!!
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)
})
}
}
//插件扩展
jQuery.prototype.dialog = function(info){
alert(info)
}
//造轮子
class myJquery extends jQuery{
constructor(selector) {
super(selector)
//新增轮子逻辑
}
}
//使用
const $p = new jQuery('p')
$p.get(2)
$p.each(ele=>console.log(ele.nodeName))
$p.on('click',()=>alert('clicked'))
3、class的原型本质,怎么理解?
知识点:class的继承、类型判断instanceof、原型和原型链
class Student {
constructor(name, age){
this.name = name
this.age = age
}
sayHi(){
console.log(
`姓名${this.name}, 年龄${this.age}`
)
}
}
//通过 new 对象实例
const tao = new Student('阿涛',120)
2、继承
//父类
class People{
constructor(name){
this.name = name
this.age = age
}
eat(){
console.log(
`姓名${this.name}, eat something`
)
}
}
//子类
class Student extends People{
constructor(name,age){
super(name)
this.age = age
}
sayHi(){
console.log( `姓名${this.name}, 年龄${this.age}` )
}
}
//子类
class Teacher extends People{
constructor(name,major){
super(name)
this.major = major
}
teach(){
console.log( `姓名${this.name}, 专业${this.major}` )
}
}
//通过 new 对象实例
const tao = new Student('阿涛',120)
const li = new Teacher('李老师','语文')
- extends关键字来继承
- super执行父类的构造函数
3、JS原型
- 隐式原型:
__proto__ - 显示原型:
prototype
子类.proto===父类.prototype //true
原型关系:
1、每个class(构造函数)都有显示原型prototype,
2、每个实例都要隐式原型__proto__,
3、实例的隐式原型__proto__指向对应class的显示原型prototype
4、原型链
三、作用域和闭包(很重要!!!)
题目:创建10个 标签点击哪个,弹出哪个的序号
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br/>'
a.addEventListener('click', function(e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}
1、作用域与自由变量
2.1作用域: 全局作用域:window对象、document对象。。。
函数作用域:只能在当前函数里使用
块级作用域:在循环,条件等语句中使用
*2.2自由变量 *
在当前作用域没有被定义,但被使用了,
向上级作用域一层一层依次寻找,直到找到为止
如果全局作用域都没找到,直接报错 xxx is not defined
2、闭包: 所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是执行的地方!!! 作用域应用的两种情况:
- (1)函数作为参数被传递
function print(fn) {
let a = 200
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn) //100
- (2)函数作为返回值被返回
function create() {
let a = 100
return function() {
console.log(a)
}
}
let fn = create()
let a = 200
fn() //100
实际开发中应用:
隐藏数据,只提供API
//缓存工具函数
function createCache() {
//闭包中的数据,被隐藏,不必外界访问
const data = {}
return {
set: function(key, val) {
return data[key] = val
},
get: function(key) {
return data[key]
}
}
}
const c = createCache()
c.set('a', 100)
console.log(c.get('a'))
3、this:取什么值,是在函数执行中确定的,不是在函数定义确定的!!!
- 作为普通函数
- 使用call、apply 、bind]()
- 作为对象方法调用
- 定时器延时器调用
- 在class方法中调用:this指的是new出来的实例对象
- 箭头函数:上级作用域的值 4-4手写bind函数:
//模拟bind
Function.prototype.bind1 = function() {
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
//获取this(数组第一项)
const t = args.shift()
const self = this
//返回一个函数
return function() {
return self.apply(t, args)
}
}
四、异步和单线程
同步和异步 的区别
手写promise加载一张图片 前端使用异步的场景有哪些?
1、单线程和异步
JS单线程语言,只能同时做一件事
浏览器和node.js已经支持JS启动进程,如Web Worker
JS和DOM渲染共用同一个线程,因为JS可以修改DOM结构
遇到等待(网络请求,定时任务)不能卡住
需要异步
回调callback函数形式
- 同步和异步是基于JS单线程语言的,异步不会阻塞代码执行,同步会阻塞代码执行
2、异步的应用场景
网络请求,如ajax图片加载
定时任务,如setTimeout
3、callback 和Promise
5-3 5-6看完es6再看
二、JS WebAPI
一、DOM
题目:
DOM属于哪种数据结构?DOM树
DOM操作的常用API:节点操作和结构操作
attr和property的区别:
- property修改的是对象属性,不会体现在html结构中;attribute修改的是html属性,会改变html结构;这两个都会引起DOM重新渲染,尽量用property
一次性插入多个DOM节点,考虑性能:先创建一个文档片段,将创建的元素一次性插入到文档片段中,最后再将文档片段插入到DOM树
知识点:
1、DOM的本质
从HTML文件解析出来的DOM树
2、DOM节点操作
1、获取DOM节点
2、attribute:设置的是标签的属性]()
3、property:修改JS变量的属性]()
3、DOM结构操作
1、新增/插入节点
2、获取子元素列表,获取父元素
3、删除子节点
4、DOM性能
- Dom操作昂贵,避免频繁查询
- 对dom查询做缓存
- 将频繁改为一次性
const listNode = document.getElementById('list')
//创建一个文档片段
const frag = document.createDocumentFragment()
for (let x = 0; x < 10; x++) {
const li = document.createElement("li")
li.innerHTML = 'List item' + x
frag.appendChild(li)
}
listNode.appendChild(frag)
二、BOM
题目:
如何识别浏览器类型?
分析拆解URL各个部分
知识点:
navigator:查看当前浏览器信息 screen:查看屏幕的宽高
location history
三、事件绑定和冒泡
编写一个事件监听函数
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
//主要是用来判断当前DOW节点是否能完全匹配对应的CSS[选择器],如果匹配成功,返回true,反之则返回false
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
//普通绑定
fn.call(target, event)
}
})
}
描述事件冒泡流程:
基于DOM树结构,事件会顺着触发元素往上冒泡,应用场景是事件委托
event.preventDefault()
无限下拉图片列表,如何监听每个图片的点击?
事件代理,用e.target获取触发元素,用matches来判断是否是触发元素
- 代码简洁、减少浏览器内存占用,在父元素上绑定,不要滥用
四、ajax
针对于浏览器-同源策略——(比如爬虫,可以拿到第三方的数据)
无视跨域的三种
<img src= 跨域的地址 />统计打点,可使用第三方统计服务 pv uv<link src= 跨域的地址 />可使用 CDN 一般是 外域<scriot src= 跨域的地址 />可实现跨域
跨域:(必须serve 端同意)
手写ajax
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr =new XMLHttpRequest()
xhr.open('GET', './data/teest.json', true)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status === 404) {
reject(new Error('404 no found'))
}
}
}
xhr.send(null)
})
return p
}
五、存储
cookie
- 本身用于浏览器和 serve 通讯
- 被借用 到 本地存储
- 可用
document.cookie = "xxx"来修改,挨个累加追加
function debounce(fn, delay = 500) {
let timer = null
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay)
}
}
input.addEventListener('keyup', debounce(function() {
console.log(input.value)
}, 1000))
function throttle(fn, delay = 100) {
let timer = null
return function() {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
const div = document.getElementById('div')
div.addEventListener('drag', throttle(function(e) {
console.log(e.offsetX, e.offsetY)
}, 600))