JS基础知识点

414 阅读17分钟

作用域

浏览器加载HTML代码的时候,首先会提供一个供全局JS代码执行的环境 -->全局作用域(window/global),全局作用域当网页关闭时才会销毁,私有作用域当
返回一个函数并被外界引用,不会销毁,其他情况下销毁

const $ = document.querySelector.bind(document)

js内存分类

栈内存:用来提供一个供js代码执行环境,作用域(全局作用域和私有作用域)
堆内存:用来存储引用数据类型的值,对象存储属性名和属性值,函数存储的是代码字符串

getBoundingClientRect

 x:元素左上角相对于视口的横坐标
 left:元素左上角相对于视口的横坐标,与x属性相等
 right:元素右边界相对于视口的横坐标(等于x加上width)
 width:元素宽度(等于right减去left)
 y:元素顶部相对于视口的纵坐标
 top:元素顶部相对于视口的纵坐标,与y属性相等
 bottom:元素底部相对于视口的纵坐标
 height:元素高度(等于y加上height)
注意,getBoundingClientRect方法的所有属性,都把边框(border属性)算作元素的一部分。也就是说,都是从边框外缘的各个点来计算。因此,width和height包括了元素本身 + padding + border。

浏览器执行机制

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。为了防止渲染出现不可预期的结果,浏览器设置 UI 渲染线程与 JavaScript 引擎线程为互斥的关系,当 JavaScript 引擎线程执行时 UI 渲染线程会被挂起,UI 更新会被保存在一个队列中等到 JavaScript 引擎线程空闲时立即被执行。

DOM添加是同步的,但是dom渲染是异步的,js线程和ui线程是互斥的,故是异步

三目运算符如果:后面不做任何操作,就写null

DOM的基础知识

属性
Node.prototype.nextElementSibling 下一个element元素节点
Node.prototype.previousElementSibling 上一个element元素节点
ParentNode.firstElementChild
ParentNode.lastElementChild
Node.prototype.parentNode 返回当前节点的父节点
element.children返回子节点

方法
Node.prototype.appendChild()
var p = document.createElement('p');
document.body.appendChild(p);

Node.prototype.cloneNode()
var cloneUL = document.querySelector('ul').cloneNode(true); 克隆出来的新节点

Node.prototype.insertBefore() 
var insertedNode = parentNode.insertBefore(newNode, referenceNode);

Node.prototype.removeChild()
divA.parentNode.removeChild(divA)

Node.prototype.replaceChild()
var replacedNode = parentNode.replaceChild(newChild, oldChild);

insertAdjacentElement() 方法将一个给定的元素节点插入到相对于被调用的元素的给定的一个位置。


document节点

document节点对象代表整个文档,每张网页都有自己的document对象

document的获取
正常的网页,直接使用document或window.document。
iframe框架里面的网页,使用iframe节点的contentDocument属性。

document.documentElement 指的是html节点
document.querySelector(),
document.querySelectorAll()
document.getElementsByTagName()
document.getElementsByClassName()
document.getElementsByName()
document.getElementById() # 
document.createElement()
document.createTextNode()
document.createComment()
document.createDocumentFragment()

Element

Element.id: 指定元素的id属性
Element.tagName: 返回指定元素的大写标签名
Element.className,Element.classList
    add():增加一个 class。
    remove():移除一个 class。
    contains():检查当前元素是否包含某个 class。
    toggle():将某个 class 移入或移出当前元素。
    item():返回指定索引位置的 class。
    toString():将 class 的列表转为字符串。
Element.dataset: 自定义data-属性
Element.innerHTML
Element.clientHeight,Element.clientWidth   width+padding
Element.clientLeft,Element.clientTop 边框宽度
Element.scrollHeight,Element.scrollWidth 
Element.scrollLeft,Element.scrollTop 表示当前元素的水平滚动条向右侧滚动的像素数量 (可读写)
Element.offsetParent 返回最靠近当前元素的、并且 CSS 的position属性不等于static的上层元素
Element.offsetHeight,Element.offsetWidth
Element.offsetLeft,Element.offsetTop Element.offsetLeft返回当前元素左上角相对于Element.offsetParent节点的水平位移,Element.offsetTop返回垂直位移,单位为像素。通常,这两个值是指相对于父节点的位移。
Element.style
Element.children
Element.firstElementChild, Element.lastElementChild
Element.nextElementSibling,Element.previousElementSibling

实例方法
getAttribute():读取某个属性的值
getAttributeNames():返回当前元素的所有属性名
setAttribute():写入属性值
hasAttribute():某个属性是否存在
hasAttributes():当前元素是否有属性
removeAttribute():删除属性
Element.querySelector()
Element.querySelectorAll()
Element.getElementsByClassName()
Element.getElementsByTagName()
Element.closest()

事件相关方法
Element.addEventListener():添加事件的回调函数
Element.removeEventListener():移除事件监听函数
Element.getBoundingClientRect()
Element.insertAdjacentElement()
    beforebegin:当前元素之前
    afterbegin:当前元素内部的第一个子节点前面
    beforeend:当前元素内部的最后一个子节点后面
    afterend:当前元素之后
Element.remove()
Element.focus(),Element.blur()
Element.click()

属性

getAttribute()
getAttributeNames()
setAttribute()
hasAttribute()
hasAttributes()
removeAttribute()
这六个方法对所有属性(包括用户自定义的属性)都适用。

Mutation Observer API

事件模型

click:按下鼠标(通常是按下主按钮)时触发。
dblclick:在同一个元素上双击鼠标时触发。
mousedown:按下鼠标键时触发。
mouseup:释放按下的鼠标键时触发。
mousemove:当鼠标在一个节点内部移动时触发。当鼠标持续移动时,该事件会连续触发。为了避免性能问题,建议对该事件的监听函数做一些限定,比如限定一段时间内只能运行一次。
mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件(详见后文)。
mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件(详见后文)。
mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件(详见后文)。
mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件(详见后文)。
contextmenu:按下鼠标右键时(上下文菜单出现前)触发,或者按下“上下文菜单键”时触发。
wheel:滚动鼠标的滚轮时触发,该事件继承的是WheelEvent接口。

MouseEvent.clientX,MouseEvent.clientY
MouseEvent.pageX,MouseEvent.pageY

进度事件

error:由于错误导致外部资源无法加载时触发。
load:外部资源加载成功时触发。

表单事件

input事件的一个特点,就是会连续触发,比如用户每按下一次按键,就会触发一次input事件。
change在元素失去焦点时发生

URL 的编码和解码

encodeURI()
encodeURI('http://www.example.com/q=春节')// "http://www.example.com/q=%E6%98%A5%E8%8A%82"

encodeURIComponent() //不能直接编码url
encodeURIComponent('http://www.example.com/q=春节')// http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82

decodeURI()
decodeURIComponent()

History

在历史移动
History.back()
History.forward()
History.go()

History.pushState()
History.replaceState()

事件
popstate 事件

渲染引擎处理网页,通常分成四个阶段

解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
布局:计算出渲染树的布局(layout)。
绘制:将渲染树绘制到屏幕。

重流和重绘

布局发生变化是重流,颜色等发生变化是重绘
重流和重绘并不一定一起发生,重流必然导致重绘,重绘不一定需要重流。比如改变元素颜色,只会导致重绘,而不会导致重流;改变元素的布局,则会导致重绘和重流。

优化技巧
读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。
缓存 DOM 信息。
不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。
使用documentFragment操作 DOM
动画使用absolute定位或fixed定位,这样可以减少对其他元素的影响。
只在必要时才显示隐藏元素。
使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
使用虚拟 DOM(virtual DOM)库。

slice

用法:arrayObj.slice(start,end)

arrayObj - 原始数组;
start - 必填;设定新数组的起始位置;如果是负数,则表示从数组尾部开始算起(-1 
指最后一个元素,-2 指倒数第二个元素,以此类推)。
end - 可选;设定新数组的结束位置;如果不填写该参数,默认到数组结尾;如果是负数,则表示从数组尾部开始算起(-1 指最后一个元素,-2 
指倒数第二个元素,以此类推)。

18.继承

function A() {}
let a = new A

a.__proto__ === A.prototype
a.__proto__.constructor === A
A.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

19.继承的完美实现

继承私有属性,以及公有属性

function Animal (name) {
  this.name = name
  this.eat = '吃肉'
}
Animal.prototype.addr = '山里'
function Tiger (name) {
  // 继承实例属性
  // Animal.call(this, ...arguments)
  Animal.apply(this, arguments)
  this.name = name
  this.age = 10
}
// 继承公共属性 方案1
Tiger.prototype.__proto__ = Animal.prototype

20.script引入方式

html 静态<script>引入
js 动态插入<script>
<script defer>: 延迟加载,元素解析完成后执行
<script async>: 异步加载,但执行时会阻塞元素渲染

21.require与import的区别

require是 同步 导入,import属于 异步 导入
(import实现懒加载路由,导出的其实是一个promise)

require是 值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化

(import('./test'))
    .then((res) => {
        console.log(res)
    })

22.改变this指向

apply call bind

23. forEach: 无法break,可以用try/catch中throw new Error来停止

24. 写 React / Vue 项目时为什么要在列表组件中写 key

key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

Set、Map、WeakSet 和 WeakMap 的区别

Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构
WeakSet存储的只能是引用类型,WeakMap的键名也只能是引用类型, 并且不会做引用计数
var test = {
	name : 'test',
	content : {
		name : 'content',
		will : 'be clean'
	}
};
var ws = new WeakSet();
ws.add(test.content);
console.log('清理前',ws);
test.content = null
console.log('清理后',ws) {}
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/6

ES5/ES6 的继承除了写法以外还有什么区别

ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)

ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。

一个继承语句同时存在于两条继承链:一条继承属性,一条实现方法的继承

1 class A extends B{}

2 A.__proto__ === B;//继承属性

3 A.prototype.__proto__ == B.prototype;//继承方法

https://www.jianshu.com/p/1aa2755171fe

setTimeout、Promise、Async/Await 区别

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/33

co库的实现原理

function * read() {
  const res = yield fs.readFile(path.join(__dirname, '1.txt'), 'utf8')
  const b = yield fs.readFile(res, 'utf8')
  return b
}

function co(it) {
  return new Promise((resolve, reject) => {
    function next(val) {
      let { value, done } = it.next(val)
      if (done) return resolve(value)
      Promise.resolve(value).then(res => {
        next(res)
      })
    }
    next()
  })
}

let it = read()

co(it).then(res => {
  console.log(res)
})

async 函数

async function async1() {
	console.log('async1 start');
	await async2();
	console.log('async1 end');
}
等价于
async function async1() {
	console.log('async1 start');
	Promise.resolve(async2()).then(() => {
                console.log('async1 end');
        })
}

模拟new实现

function _new(fn, ...arg) {
    const obj = Object.create(fn.prototype);
    const ret = fn.apply(obj, arg);
    return ret instanceof Object ? ret : obj;
}

http三次握手和四次挥手

github.com/Advanced-Fr…

https详解

https采用的是对称加密以及非对称加密来做的,传输肯定有2个端,客户端以及服务端,服务端会给客户端自己的公钥,客户端采用对称加密加密传输的内容,同时用公钥加密自己的对称加密的秘钥,然后把内容传输到服务端,服务端用私钥解密对称加密的秘钥,从而解密内容。 CA证书,可以类比安全局,记录服务端的公司信息以及公钥,从而判断服务端传输过来的公钥是不是服务端自己的。

juejin.cn/post/684490…

npm install

发出npm install命令
查询node_modules目录之中是否已经存在指定模块
若存在,不再重新安装
若不存在
npm 向 registry 查询模块压缩包的网址
下载压缩包,存放在根目录下的.npm目录里
解压压缩包到当前项目的node_modules目录

观察者模式

Object.defineProperty

let targetVal = {
  name: 'xx'
}

let name = targetVal.name

function observe() {
  console.log('xx')
}

Object.defineProperty(targetVal, 'name', {
  get() {
    return name
  },
  set(val) {
    observe()
    name = val
  }
})

targetVal.name = 'ass'

聊聊 Redux 和 Vuex 的设计思想

共同点:都是为响应式编程提供的一个的可预测的状态容器。方便在复杂的应用中进行兄弟组件或者子组件里修改状态。
不同点:状态改变时 redux 通过纯函数(reduce)生成新的 state, 而vux是直接修改状态属性,最后出发相应的跟新操作

浏览器和Node 事件循环的区别

Node 11以后:
和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。

let const

let a = 10;
const b = 20;
相当于:
(function(){
         var  a = 10;
         var b = 20;
})()

xss

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。

<template>
  <div>
    <span v-html="attack"></span>
  </div>
</template>
<script>
export default {
  data () {
    return {
      attack: '<img src="" onerror="alert(1)"></img>' // eslint-disable-line
    }
  }
}
</script>

cookie 和 token 都存放在 header 中,为什么不会劫持 token?

1、首先token不是防止XSS的,而是为了防止CSRF的;
2、CSRF攻击的原因是浏览器会自动带上cookie,而浏览器不会自动带上token

cookie:登陆后后端生成一个sessionid放在cookie中返回给客户端,并且服务端一直记录着这个sessionid,客户端以后每次请求都会带上这个sessionid,服务端通过这个sessionid来验证身份之类的操作。所以别人拿到了cookie拿到了sessionid后,就可以完全替代你。

token:登陆后后端不返回一个token给客户端,客户端将这个token存储起来,然后每次客户端请求都需要开发者手动将token放在header中带过去,服务端每次只需要对这个token进行验证就能使用token中的信息来进行下一步操作了。

xss:用户通过各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本获取信息,发起请求,之类的操作。

csrf:跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。csrf并不能够拿到用户的任何信息,它只是欺骗用户浏览器,让其以用户的名义进行操作。

csrf例子:假如一家银行用以运行转账操作的URL地址如下: http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
那么,一个恶意攻击者可以在另一个网站上放置如下代码: <img src="<http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman>">
如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。

上面的两种攻击方式,如果被xss攻击了,不管是token还是cookie,都能被拿到,所以对于xss攻击来说,cookie和token没有什么区别。但是对于csrf来说就有区别了。

以上面的csrf攻击为例:

cookie:用户点击了链接,cookie未失效,导致发起请求后后端以为是用户正常操作,于是进行扣款操作。
token:用户点击链接,由于浏览器不会自动带上token,所以即使发了请求,后端的token验证不会通过,所以不会进行扣款操作。
这是个人理解的为什么只劫持cookie不劫持token的原因。

浏览器缓存

www.jianshu.com/p/54cc04190… juejin.cn/post/684490…

图片懒加载的实现

juejin.cn/post/684490… 通过img元素的getBoundingClientRect().top 与document.docuElement.clientHeight比较,来判断是否是在可视区域 可以用函数节流去优化代码 blog.csdn.net/weixin_3892…

节流和防抖

const throttle = (fn, interval) => {
  let firstTime = true
  let timer = null
  return function(...args) {
    if (firstTime) {
      return fn.apply(this, args)
    }
    if (timer) return
    timer = setTimeout(() => {
      clearTimeout(timer)
      timer = null
      fn.apply(this, args)
    }, interval)
  }
}

const debounce = (fn, interval) => {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    let callNow = !timer
    timer = setTimeout(() => {
      clearTimeout(timer)
      timer = null
    }, interval)
    if (callNow) fn.apply(this, args)
  }
}

co库

function co(it) {
  return new Promise((resolve, reject) => {
    function next(val) {
      let { value, done } = it.next(val)
      if (done) return resolve(value)
      Promise.resolve(value).then((res) => {
        next(res)
      })
    }
    next()
  })
}

call apply bind

Function.prototype.newCall = function (context,...params) {
  if (typeof context === 'object') {
      context = context || window
  } else {
      context = Object.create(null);
  }
  let fn = Symbol();
  context[fn] = this
  var result = context[fn](...params);
  
  delete context.fn;
  return result;
}


Function.prototype.newApply = function(context, parameter) {
  if (typeof context === 'object') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  let fn = Symbol()
  context[fn] = this;
  var result = context[fn](...parameter);
  delete context[fn];
  return result;
}

Function.prototype.newBind = function(context, ...args) {
  if (typeof context === 'object') {
    context = context || window
  } else {
    context = Object.create(null)
  }
  return (...rest) => {
    this.apply(context, [...args, ...rest])
  }
}

function bindFn (...args) {
	let context = args.shift() // 第一个参数为绑定的this
	if (!context) throw new Error('no "this" object')
	return (...rest) => {
		// 这里的this指的是当前调用该方法的函数对象
		this.apply(context, [...args, ...rest])
	}
}
let testObj = {
	value: 5
}

let testFn = function (a, b, c) {
	console.log(this.value, a, b, c)
}

testFn.bindFn = bindFn
testFn = testFn.bindFn(testObj, 666, 888)
testFn(999) // 5 666 888 999

js的new操作符做了哪些事情

new 操作符新建了一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象。

如何实现sleep的效果(es5或者es6)

function sleep(ms){
   var start=Date.now(),expire=start+ms;
   while(Date.now()<expire);
   console.log('1111');
   return;
}

options请求

OPTIONS请求即预检请求,可用于检测服务器允许的http方法。当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起OPTIONS请求,即CORS预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。

介绍 HTTPS 握手过程

客户端使用https的url访问web服务器,要求与服务器建立ssl连接
web服务器收到客户端请求后, 会将网站的证书(包含公钥)传送一份给客户端
客户端收到网站证书后会检查证书的颁发机构以及过期时间, 如果没有问题就随机产生一个秘钥
客户端利用公钥将会话秘钥加密, 并传送给服务端, 服务端利用自己的私钥解密出会话秘钥
之后服务器与客户端使用秘钥加密传输

proxy

let obj = {
  name: 'yyc',
  age: 23
}

let p = new Proxy(obj, {
  get(target, props) {
    console.log(target, props)
    // return Reflect.get(target, props)
    return target[props]
  },
  set(target, props, value) {
    console.log(target, props, value)
    target[props] = value
  }
})

console.log(p.name)

解构赋值

剩余运算符 只能用在最后一项

let [,...args] = ['xxx',10,'xxxx']
let {name,...args} = {name:'xxx',age:10};

defineProperty

configurable: true // 是否可以删除
writable: // 是否可重写
enumerable: // 是否可枚举

class 静态属性

子类的super是父类构造器

// 静态属性
static get flag() {
    return 'flag';
}
// 静态方法
static getName () {}

mockNew

function mockNew(A){
    let obj = {}
    let returnVal = A.call(obj);
    if((typeof returnVal === 'object' && returnVal !== null) || typeof returnVal === 'function'){
        return returnVal;
    }
    obj.__proto__ = A.prototype
    return obj;
}

装饰器

装饰器只能是函数

  1. 装饰器修饰类,参数是类的构造器
  2. 装饰器修饰类的属性和方法,参数是类的原型,修饰的key,key对应的值
function type1(type1) {
    return function(Constructor) {
        Constructor.type1 = type1
    }
}

function before(prototype, key, descriptor) {
    const oldVal = descriptor.value
    descriptor.value = function(...args) {
        oldVal.apply(this, ...args)
    }
}

@type1('ass')
class A {
    name = 'ass'
    @before
    say() {
        console.log(this.name)
    }
}

let a = new A

a.say()

compose

function compose(...args) {
  return function(...values) {
    let fn = args.pop()
    return args.reduceRight((prev, next) => {
      return next(prev)
    }, fn(...values))
  }
}

Promise.allSettled

类似Promise.all,但是all如果有一个reject就会走catch,但是allSettled不会,他包括reject以及resolve