JS知识点梳理

    技术2022-07-12  75

    前言

    快要开始面试了,在这里记录各种前端知识点,方便自己复习。每一个问题下不止记录该问题的答案,还会记录所有相关的知识点。欢迎大家一起交流学习。

    变量类型和计算

    1.typeof能判断哪些类型?

    值类型:undifined、string、number、布尔、symbol(ES6) 引用类型:对象、数组、null(特殊,指针指向空地址)、函数 typeof可以识别所有的值类型,识别函数,但是只能判断是否为引用类型object(不可细分) 手写实现深拷贝:

    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){ if(obj.hasOwnProperty(key)){ result[key] = deepClone(obj[key]) } } return result; }

    变量计算 1.字符串转换为数字可以用parseInt(),因为在有字符串 + …的式子,会强制把其他变量也转换为字符串 2.‘= =’:能强制转换之后相等的,会判断为true,如:100==‘100’,0==‘ ’,0 = =false ,null = = undifined;那么在什么时候使用= = 呢?一般只有这种情况: if(obj.a == null) 来判断obj.a是否为null或者undifined。 3.if语句判断的是truely变量(!!a = = true)和false变量(!!a = = false),以下为所有的falsely变量:

    !!0 === false; !!NaN === false; !!undefined === false; !!null === false; !!'' === false; !!false === false;

    判断arr是否是一个数组的方法: 1.Array.isArray(arr) 2.arr instanceof Array

    原型和原型链

    如何判断一个变量是不是数组? 手写一个简易的Jquery,考虑插件和扩展性 class的原型本质,怎么理解?

    以一个class继承的代码为例

    class People{ constructor(name){ this.name = name; } eat(){ console.log(`${this.name} eat rice`); } } class Student extends People{ constructor(name,number){ super(name); this.number = number; } sayHi(){ console.log(`我叫${this.name},我的学号是${this.number}`); } } let xiaoming = new Student('小明', 12345);

    instanceof:1. 判断new出来的实例对象是属于某一类(xiaoming instance of People,xiaoming instanceof Student, xiaoming instance of Object都为true ) 2.[] instanceof Array 判断是否为数组 3.{} instanceof Object 判断是否为对象 4.[] instanceof Object 也会返回true 原型 每个class都有显式原型prototype; 每个实例都有隐式原型__proto__; 实例的__proto__指向对应class的prototype; 原型链 当xiaoming instanceof时,就相当于沿着xiaoming的__proto__去查找原型链对应的prototye,如果有那么该prototype对应的类就为true,否则为false.

    作用域和闭包

    this的不同应用场景,如何取值? 手写bind函数 实际开发中闭包的应用场景,举例说明

    自由变量:当前作用域未定义,但使用了。那么就向上级作用域去查找,如果一直找到全局作用域都没找到,那就报错:xxx is not difined. (闭包中)自由变量的查找

    //函数作为返回值 function create(){ const a = 100; return function fn(){ console.log(a); } } const re = create(); const a = 200; re(); // 函数作为传参 function print(fn){ const a = 200; fn(); } const a = 100; function fn(){ console.log(a); } print(fn);

    结论:自由变量的查找是在函数定义的地方向上级作用域查找,不是在执行的地方

    闭包的影响 变量会常驻内存,得不到释放,所以闭包不要乱用,但是闭包并不一定会造成内存泄漏(因为内存泄漏是指垃圾变量没有被释放,而闭包中的变量不一定无用)

    this this取什么值,是在函数执行的时候确认,而不是在函数定义的时候确认。 箭头函数中的this,取它上级作用域的值,它自己没有this 以下是包含this的几种情况

    ```javascript //情况一 function fn1(){ console.log(this); } fn1(); //window fn1.call({x:100}); //{x:100} fn2 = fn1.bind({x:200}); fn2();//{x:200} //情况二 const zhangsan = { name: '张三', sayHi(){ console.log(this); //zhangsan这个对象 }, wait(){ setTimeout(function(){ console.log(this)//window },1000) } } //情况三 const lisi = { name: '李四', sayHi(){ console.log(this); //lisi这个对象 }, wait(){ setTimeout(() => {console.log(this)},1000)//lisi这个对象 } }

    this的使用场景及取值: 1.当做普通函数被调用——全局 2.使用call apply bind——指向传入的参数 3.作为对象方法调用——指向上级对象,函数属于谁就指向谁 4.在class的对象中调用——实例本身 5.箭头函数中——找上级作用域中this的值来确定 手写bind函数

    Function.prototype.myBind = function(){ const args = Array.prototype.slice.call(arguments); const a = args.shift(); const self = this; return function(){ return self.call(a, args); } }

    异步和单线程

    同步和异步的区别是什么? 手写用promise加载一张图片 前端使用异步的场景

    JS是单线程语言,只能同时做一件事儿 浏览器和nodejs支持JS启动进程,如web worker JS和DOM渲染共用同一个线程,因为JS可以修改DOM结构

    同步和异步的区别: 1.同步会阻塞代码执行,异步不会阻塞代码执行

    前端使用异步的场景: 1.网络请求:如ajax图片加载 2.定时,如setTimeout

    手写promise加载图片

    const url1 = 'https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png'; const url2 = 'https://pics5.baidu.com/feed/eac4b74543a98226dbbad777b05f51074b90eb4f.jpeg?token=27a371d8135063b0f0b46b46bfe244b6'; function loadImg(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; }) } // loadImg(url1).then((img) => // {console.log(img.width) // return img}).then((img) => { // console.log(img.height) // }).catch((err) => console.error(err)) loadImg(url1).then((img) => {console.log(img.width) return img//返回一个普通对象 }).then((img) => { console.log(img.height); return loadImg(url2); //返回promise对象 }).then(img2 =>{ console.log(img2.width); return img2; }).then( img2 => { console.log(img2.height); }).catch((err) => console.error(err))

    以下开始过渡到js的web api

    DOM(Document Object Model)

    DOM是哪种数据结构? DOM操作常用API attribute和property的区别 一次性插入多个DOM节点,考虑优化

    DOM的本质:从html文件解析出来的树 获取DOM节点的方法: 1.document.getElementById( ) //元素 2.document.getElementsByTagName() //集合 3.document.getElementsByClassName() //集合 4.document.querySelectorAll()//集合

    attribute和property的区别:

    let plist = document.querySelectorAll('p'); let p = plist[0]; // propety是通过获取属性开改变页面样式的一种形式 p.style.width = '100px'; p.className = 'red'; console.log(p.style.width); console.log(p.className); console.log(p.nodeName); console.log(p.nodeType); //attribute p.setAttribute('data-name', 'jiang'); console.log(p.getAttribute('data-name')) p.setAttribute('style', 'font-size:50px')

    1.property:修改对象属性,不会修改到对象属性中; 2.attribute:修改html属性,修改html结构; 3.两者都可能会引起DOM的重新渲染(所以建议使用property) 以上三点为看mooc视频时的总结,根据我自己的理解来讲,我认为这两者之间的区别还在于property只能修改DOM自带的属性,而attribute不仅可以修改dom自带的属性,还可以修改和创建一些自定义属性;

    DOM结构操作:

    const div1 = document.getElementById('div1'); const div2 = document.getElementById('div2'); //创建节点 const newP = document.createElement('p'); newP.innerHTML = 'this is newP'; //插入节点 div1.appendChild(newP); //移动节点 const p1 = document.getElementById('p1'); div2.appendChild(p1); //获取父元素 console.log(p1.parentNode); //获取子元素 console.log(div1.childNodes); const div1ChildsNodeP = Array.prototype.slice.call(div1.childNodes).filter(child =>{ if(child.nodeType === 1){ return true; }else{ return false; } }) console.log(div1ChildsNodeP);//仅为标签的子元素 //删除子元素 div1.removeChild(div1ChildsNodeP[0]);

    DOM性能: 1.缓存DOM查询结果 2.将频繁操作改成一次操作

    const list = document.getElementById('list'); //创建一个文档片段,此时还没有插入DOM中,可以避免多次DOM操作 const frag = document.createDocumentFragment(); for(let i =0;i<10;i++){ const li = document.createElement('li'); li.innerHTML = `this is li${i}`; frag.appendChild(li);//此处没有操作DOM } list.appendChild(frag);//此处操作了一次DOM

    DOM的数据结构:树

    BOM(Browser Object Model)

    如何识别浏览器类型 分析拆解url的各个部分

    //navigator const ua = navigator.userAgent;//拿到浏览器的信息(包括类型) const isChrome = ua.indexOf('Chrome'); console.log(isChrome) //screen console.log(screen.width); console.log(screen.height); //location console.log(location.herf);//取网址 console.log(location.protocol); //取协议(http或https) console.log(location.host)//取域名 console.log(location.search)//取参数 console.log(location.hash);//取hash console.log(location.pathname)//取路径 //history history.back(); history.forward();//网页前进或后退

    事件绑定和事件冒泡

    编写一个通用的事件监听函数 描述事件冒泡的流程 无限下拉的图片列表,如何监听每个图片的点击

    通用的事件监听函数

    function bindEvent(item, type,selector,fn){ if(fn == null){ fn = selector; selector = null } item.addEventListener(type,event =>{ let targrt = event.target; if(selector){//事件代理绑定 if(targrt.maches(selector)){ fn.call(target,event); } }else{//普通绑定 fn.call(target,event); } }); }

    ajax

    手写一个简易的ajax 跨域的常用实现方式

    手写ajax

    function ajax({body,headers,url,method}) { return new Promise((resolve,reject) =>{ let request = new XMLHttpRequest(); request.open(method,url); for(let key in headers){ let value = headers[key]; request.setRequestHeaders(key,value); } request.send(body); request.onreadystatechange = () =>{ if(request.readyState === 4){ if(request.status === 200){ resolve( JOSN.parse(request.responseText) ) }else if(request.status === 400){ reject(new Error('request failed')) } } } }) }

    xhr.readyState: 1.0-(未初始化)还没有调用send()方法; 2.1-(已调用send()方法)还在发送请求; 3.2-(载入完成)send()方法执行完成,已经接收到全部响应内容; 4.3-(交互)正在解析响应内容; 5.4-(完成)响应内容解析完成,可以在客户端调用

    xhr.status(状态码): 2xx:表示成功处理请求,如200 3xx:需要重定向,浏览器重新跳转,301(永久重定向),302(临时重定向),304(资源未改变) 4xx:客户端请求错误,404(服务端找不到,请求错误),403(客户端没有权限) 5xx:服务端出错

    同源策略: ajax请求时,浏览器要求当前网页和server必须同源(安全) 同源:协议、域名、端口号三者都相同 img、link、script三个标签不受同源策略限制 img标签可用于统计打点,可使用第三方统计服务 link和script可使用CDN,CDN一般都是外域 script可以实现JSONP,后端也可以根据前端传来的拼接地址进行数据选择来传给前端 所有的跨域,都必须经过server端的允许和配合 cors–设置服务端http header也可以实现跨域: 待补充…

    axios作为ajax插件,本质是对XMLHttpRequest进行了封装,它支持promise

    存储

    cookie localstorage sessionstorage的区别 cookie本身用于浏览器和server通讯,可以通过document.cookie修改,也用于本地存储。cookie的值是添加形式的,每次赋值只要没有对cookie中的同一变量就行重赋值,此次赋值不会覆盖之前的值,会直接添加在后面 cookie的特点: 存储大小,最大4KB http请求时需要发送到服务端,增加请求数据量 只能用document.cookie = ‘…’,太过简陋 localstorage 和sessionstorage: 存储大小最大为5M API简单易用,setItem ,getItem 不会随着http请求被发出去 localstorage数据会永久存储,除非代码手动删除,sessionstorage只存在于当前会话,浏览器关闭则清空。

    开发环境

    git 抓包 webpack babel linux的常用命令

    git常用命令(按实际项目中使用顺序来写) git branch(检查当前状况下代码分支) git checkout -d xxx (创建一个新的分支) git status(检查当前的修改文件有哪些) git show(检查修改内容) git add . xxx(提交修改) git checkout(撤销上一步修改) git push origin xxx(推送到服务端) git pull origin xxx (从服务端下载) git merge(合并分支) 抓包 chales实现代理网站,手机连接电脑进行抓包,以及下载证书来获取https的信息 webpack和babel 使用原因: 1.ES6模块化,浏览器暂不支持 2.ES6语法,浏览器并不完全支持 3.压缩代码、整合代码,以让网页加载更快

    webpack基础配置 1.仅执行打包(webpack.config.js)

    const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', //production 开发/线上模式 entry: path.join(__dirname, 'src', 'index.js'), //找到当前目录下的src的index.js output:{ filename: 'bundle.js', path: path.join(__dirname, 'dist') }, plugins: [ new HtmlWebpackPlugin({ template: path.join(__dirname, 'src', 'index.html'), filename: 'index.html', //产出文件名,放在dist中 }) ], devServer:{ port: 3000, contentBase: path.join(__dirname, 'dist') } }

    2.将ES6转换为ES5 需要安装babel,配置如下 添加.babelrc文件

    { "presets": ["@babel/preset-env"] }

    在webpack.config.js中添加

    module:{ rules:[ { test: /\.js$/, loader:['babel-loader'], include: path.join(__dirname, 'src'), exclude: /node_modules/ } ] },

    3.ES6模块化 如果需要导出多个模块,用export一次性导出,或者挨个导出,然后用解构赋值的方法来获取这些模块 如果只导出一个模块,那就要用export default 但是导出多个的时候如果用了default,就不能再用解构赋值获取了,只能单个导出之后,采用属性的方法来获取模块中的内容

    export function fn(){ console.log(fn); } export const age = 10; export const obj = { name: 'zhangsan' } function fn(){ console.log(fn); } const age = 10; const obj = { name: 'lisi' } export { fn, age, obj, } const xxx = { nema: 'xxx' } export default xxx

    4.webpack配置生产环境 需要修改的部分:

    mode: 'production', //这里改了 entry: path.join(__dirname, 'src', 'index.js'), //找到当前目录下的src的index.js output:{ filename: 'bundle.[contenthash].js',//这里改了 path: path.join(__dirname, 'dist') },

    有需要的话还可以配置一下package中的build命令 这里在bundle文件名后面加的cotenthash可以随机生成一个乱码,当文件被修改后,它就会改变,有利于性能优化和缓存,后面还会说到 Linux常用命令 ssh 用户名@IP地址 (登录线上机) ls/li(查看文件夹) clear (清屏) mkdir xxx (创建文件夹) rm -rf xxx (删除文件夹) cd xxxxxx (进入某目录) mv xxx yyy (把文件名xxx改为yyy) mv xxx …/…/…/xxx (修改文件路径) cp xxx xxx1(把xxx拷贝到xxx1) touch xxx(新建文件) vi/vim xxx(新建文件并打开,打开之后可以写入内容,i进入编辑模式,esc后退,:w保存, :q退出,:q!强制退出) cat xxx(查看文件内容) grep “关键字” xxx (在文件xxx中查找关键字)

    运行环境

    网页加载过程 性能优化 安全

    从输入url到渲染出页面的整个过程 要加载html代码,媒体文件,如图片、视频等,javajscript css 1.DNS解析:域名 -> ip地址 2.浏览器根据IP地址向服务器发起http请求(包括TCP连接) 3.服务器处理http请求,并返回给浏览器 4.网页根据HTML代码生成DOM Tree,根据CSS代码生成CSSOM 5.将DOM Tree 和CSSOM 整合形成 Render Tree 6.浏览器根据Render Tree渲染页面 7.遇到script标签则暂停渲染,优先加载并执行JS代码,完成再继续 8.直至把 Render Tree渲染完成

    为什么要把CSS放在head里,把JS放在body最后呢? 因为CSS里包含的样式最好是在DOM加载时就加载完,然后和DOM Tree一起渲染,否则每次改变样式都造成重新渲染,很麻烦 JS放在不放在最后,会因为JS导致渲染卡住

    图片加载不会阻塞渲染

    window.onload和DOMContentLoaded的区别: window.onload页面的全部资源加载完才会执行,包括图片、视频 DOMContentLoaded在DOM渲染完即可执行,此时图片、视频可能还没有加载完

    性能优化 1.原则 多使用内存、缓存等方法,减少CPU计算量,减少网络加载耗时(空间换时间) 2.从何入手 一、让加载更快(1)减少资源体积:压缩代码(如webpack打包压缩) (2)减少访问次数:合并代码。SSR服务器渲染,缓存 (如CSS精灵图、webpack打包时给出口文件名加上contenthash) (3)使用更快的网络:CDN 二、让渲染更快:(1)css放在head,JS放在body最下面 (2)尽早执行JS,用DOMContentLoaded触发 (3)懒加载(图片懒加载,上滑加载更多) (4)对DOM查询进行缓存(见前面) (5)频繁操作DOM,合并到一起插入DOM结构(见前面) (6)节流throttle,防抖debounce

    contenthash的原理: 静态资源加hash后缀,根据文件内容计算hash 文件内容不变,则hash不变,则url不变 Url和文件不变,则会自动触发http缓存机制,返回304

    CDN:用来配置静态文件,CDN不变的话也会返回304

    SSR:服务端渲染——将网页和数据一起加载,一起渲染 如果是非SSR(前后端分离)——先加载网页,再加载数据,再渲染数据

    懒加载(待补充) 给图片的src设置为preview.png,然后将真正的图片地址赋值给一个自定义属性,当屏幕滑动到图片的位置时,再显示图片

    防抖debounce 监听一个输入框,文字变化后触发change事件 直接用keyup事件,则会频繁触发chenge事件 防抖:用户输入结束或暂停时,才会触发change事件 手写防抖:

    function debounce (fn, delay = 500){ let timer = null; return function (){ if(timer){ clearTimeout(timer); } timer = setTimeout(() =>{ fn(this,arguments) timer = null; },delay) } } input1.addEventListener('keyup', debounce(() => { console.log(input1.value) }),600)

    节流throttle(用于比防抖更频繁的触发) 拖拽一个元素时,要随时拿到该元素被拖拽的 位置 直接用drag事件,则会频繁触发,很容易导致卡顿 节流:无论拖拽速度多快,都会每隔一定时间触发一次

    function throttle(fn,delay){ let timer = null; return function(){ if(timer){ return } timer = setTimeout(() =>{ fn.apply(this,arguments); timer = null; },delay) } }

    安全

    常见的web前端攻击方式有哪些? XSS跨站脚本攻击 CSRF 跨站请求伪造

    XSS跨站脚本攻击 如:一个网站,我在其中写了一个script标签,里面包含获取用户cookie的代码,当用户点入网站时,即可获取用户的cookie,发送到服务端

    XSS预防: 1.替换特殊字符,如<变为< >变为>

    Processed: 0.010, SQL: 10