# JavaScript
# 原型链
# 基础概念
- prototype
每一个函数都有一个
prototype
属性,这个属性指向函数的原型对象。 - __proto__
每个对象(除null外)会有
__proto__
属性,这个属性会指向它的构造函数的原型对象 - constructor
- 每个原型都有一个
constructor
属性,指向该关联的构造函数。 - 通过构造函数生成的实例,也会继承这个属性,而通过这个属性,可以知道实例是由哪一个构造函数生成的,我们也可以通过
实例.constructor
间接调用构造函数 - 在修改原型对象后,一般要设置下
constructor
,防止出错
- 每个原型都有一个
function Person(name) {
this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
method: function () {}
};
// 修改后constructor不再执行构造函数
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
# 何为原型链
既然每个对象都有__proto__
属性,那么函数的prototype
所指向的原型对象也会有__proto__
属性,而这样一级一级的指向直到null,就构成了所谓的原型链。
// 构造函数
function Person() {
}
var person = new Person();
// 实例对象的原型指向了它的构造函数的原型对象(prototype)
console.log(person.__proto__ === Person.prototype) // true
// 构造函数原型对象的原型指向了Object原型对象
console.log(Person.prototype.__proto__ === Object.prototype) // true
// Object原型对象的原型指向了null
console.log(Object.prototype.__proto__ === null) // true
// 所谓的原型链,就如上面三个,最终的指向都是null
console.log(Person.prototype.constructor == Person) // true
# 继承
# 原型链继承
当尝试访问一个对象的属性时,它不仅会在该对象上搜索,还会搜索该对象的原型,以及该对象的原型的原型,直到找到该属性或查到原型链的顶端。
function A() {
}
function B() {
}
A.prototype.name = 'cat'
// B继承A
B.prototype = new A()
let c = new B()
console.log(c.name) // cat
缺点也显而易见
- 子类的所有的实例共享父类的属性,修改了父类上的属性,所有实例的值也会跟着改变
A.prototype.name = 'dog'
console.log(c.name) // dog
# 构造函数继承
# JS模块化/AMD CMD的区别
# 闭包的场景
# 跨域怎么做?jsonp的风险?xss攻击怎么防御?xss的原理
# 什么是跨域
由浏览器的同源策略引起的跨域问题。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 同源策略一般限制一下行为
Cookie、LocalStorage 和 IndexDB 无法读取
DOM 和 Js对象无法获得
AJAX 请求不能发送
# 跨域方法
- jsonp
- document.domain + iframe跨域
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
- 跨域资源共享(CORS)
- 服务器反向代理
- nginx代理跨域
- apche代理跨域
- node代理跨域
- WebSocket协议跨域
# jsonp
虽然浏览器有同源策略,但是它并不禁止某些特定的标签去加载不同域下的资源,因此可以通过这个方法去实现跨域。 一般,我们可以动态的创建script标签,再去请求一个带参网址来实现跨域通信
let script = document.createElement('script');
script.src = 'http://www.example.com?username=Nealyang&callback=callback';
document.body.appendChild(script);
function callback(res) {
console.log(res);
}
在jQuery中,我们可以直接通过ajax实现
$.ajax({
url:'http://www.example.com',
type:'GET',
dataType:'jsonp',
jsonpCallback:'callback',
data:{
}
})
它的原理也让它只能实现get请求
# document.domain + iframe跨域
这个方法要求他们的主域名相同,类似a.exmaple.com
和b.example.com
。
实现方法就是把两个网站的document.domain
设置为example.com
# postMessage
window.postMessage() 方法可以安全地实现跨源通信。
如果不想发给别人或者,这设定好targetOrigin
如果不想接受其他人的信息,则在监听message
时间时,使用origin
和source
属性验证发件人的身份
# cors(主流)
服务端设置Access-Control-Allow-Origin
即可。
如需传递cookie,后台需要设置Access-Control-Allow-Credentials
为true
,前端要在ajax
中打开withCredentials
,
如果要带cookie请求,那么Access-Control-Allow-Origin
就不能设为*
,因为cookie还是要遵循同源策略。
cors分为简单请求和非简单请求,非简单请求会在正式请求前增加一次OPTIONS请求用于预检
# 服务器端反向代理
主要配置apache\node\nginx的反向代理,因为同源策略是浏览器的,服务器不受限制,可以将服务器作为跳板实现跨域
# websocket
比较新的、潮流的方式,能够真正实现客户端与服务器的双向实时通信,也允许跨域通信
# location.hash + iframe (没实际用过)
原理就是通过锚点和iframe实现传值,大概做法就是,a域名下的a页面放一个隐藏的iframe,src指向b域名下的b页面,b页面也有一个隐藏的iframe指向a域下的c页面, b页面监听hash改变后,修改b页面的iframe的src,c页面在监听到变化后,就可以操作a页面的回调了(a页面与c页面在一个域下)
主要通过定时器来监听hash变化
# window.name + iframe跨域 (没实际用过)
window.name
有个特性就是,无论url怎么变,window.name
是不变的,因此实现方式就比较简单了。
a域下的a页面加载一个iframe,src指向b域名下的b页面,b页面设置window.name
。此时由于同源策略,a并不能直接获取window.name
,但是我们可以在a页面加载完iframe后,通过iframe.contentWindow.location
将src改为a域名下的某个页面,这样iframe就和a页面是同源的了,通过iframe.contentWindow.name
获取b页面的值
# 打包工具用过吗?
# 前端项目部署:手动部署 vs CI/CD
# 前端项目部署有没有考虑CDN
# Vue的实现原理
# Vue-router的原理,两种模式的区别
# 小程序技术选型: mpvue/taro/uni-app
只用过原生开发,港真,原生小程序挺够用,虽然常常会有一些神奇的bug
# 手写ajax和jsonp
# 手写promise
# 防抖函数(debounce)
# 节流函数(throttle)
# 深克隆(deepclone)
# 实现instanceOf
# 模拟new
# 原理
- 创建一个空对象
- 将空对象原型链
__proto__
指向构造函数的原型prototype
- 将这个空对象赋值给函数内部的this关键字
- 执行构造函数内部代码,并返回该对象
# 模拟实现
function _new() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' && result !== null ? result : obj
}
# 实现一个call
# 定义
该方法主要用于改变this指向
let obj = {};
let f = function () {
return this;
};
// 默认指向全局
f() === window // true
// 通过call将f下的this指向变为obj
f.call(obj) === obj // true
非严格模式下,call参数如果为空、null和undefined,则默认传入全局对象;如果参数是一个非对象,会自动转换为对应的包装对象。 该方法可以接受多个参数。
func.call(thisValue, arg1, arg2, ...)
第一个参数为this指向的对象,后面的参数表示函数需要的参数
# 实现
首先需要了解一点this的指向
let obj = {
name: 'cat',
def: function() {
return this.name
}
}
obj.def() // ‘cat', this指向obj
调用对象中的方法,其this指向的是该对象,利用该原理:
Function.prototype.call = function(context) {
const cxt = context || window
cxt.func = this // this指向的是调用它的函数
const args = Array.from(arguments).slice(1)
const res = args.length ? cxt.func(...args) : cxt.func()
delete cxt.func
return res
}
缺点就是如果传进来的对象是通过Object.freeze()创建的,就无法使用了
Function.prototype.myCall = function(context) {
const cxt = context || window
const fn = this
const args = Array.from(arguments).slice(1)
fn.apply(cxt, args)
}
# 实现一个apply
# 定义
和call一样,唯一的差别就是接受的第二个参数是一个数组,而call是一个参数列表
# 实现
Function.prototype.apply = function(context) {
const cxt = context || window
cxt.func = this
const args = Array.from(arguments)[1]
const res = args.length ? cxt.func(...args) : cxt.func()
delete cxt.func
return res
}
# 实现一个bind
# 定义
bind的实现和call很相似,但是bind可以将参数分批次传入,并且返回的是一个函数
# 实现
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
let args = Array.from(arguments).slice(1) // bind时传递的参数
const fn = function(){
const funArg = Array.from(arguments) // bind后返回函数传递的参数
args =[].concat.apply(args, funArg); // 合并参数
_this.apply(this instanceof _this ? this : context, args);
}
fn.prototype = this.prototype
return fn
}
# 模拟Object.create
# 实现JSON.parse
# 扁平化数组/flat
# 格式化数字,千分位分隔
# 回文判断
# 发布订阅模式
# 去重
# 实现一个Vue双向绑定
# 实现一个JS函数柯里化
# 实现数组解构destructuringArray方法
# 实现sleep
# 同时加载十万个元素
# 冒泡排序
# 选择排序
# 插入排序
# 快速排序
# 爬楼梯问题/斐波纳契数列
# 二分法
# 事件循环
# 变量提升
# this指向
# 定义
简单理解来说,this就是属性或方法“当前”所在的对象。
通过举例来说
window.name = '王五'
function f() {
console.log('姓名:'+ this.name)
}
let A = {
name: '张三',
describe: f
};
let B = {
name: '李四',
describe: f
};
f() // "姓名:王五"(全局环境的this指向window)
A.describe() // "姓名:张三"
B.describe() // "姓名:李四"
let C = A.describe
C() // 王五
# 使用
- 全局环境的this,一般指向的是window
- 构造函数的this,指向的是他的实例对象,所以this属性就是实例对象上的属性
- 对象方法中的this,指向方法运行时所在的对象
# 正则
# 数据类型及判断
# 基本数据类型
- String
- Number
- Boolean
- Null
- undefined
- Symbol
- BigInt
- Object
# 判断
const bool = true
const num = 1
const str = 'abc'
const und = undefined
const nul = null
const arr = [1,2,3]
const obj = {name:'haoxl',age:18}
const fun = function(){console.log('I am a function')}
const big = 10n
- typeof
console.log(typeof bool); //boolean
console.log(typeof num);//number
console.log(typeof str);//string
console.log(typeof und);//undefined
console.log(typeof nul);//object
console.log(typeof arr);//object
console.log(typeof obj);//object
console.log(typeof fun);//function
console.log(typeof big);//bigint
对于null、array、function无法进一步判断
- instanceof
检测
constructor.prototype
是否存在于参数object
的原型链,一般用于判断对象的类型 - constructor
console.log(bool.constructor === Boolean);// true
console.log(num.constructor === Number);// true
console.log(str.constructor === String);// true
console.log(arr.constructor === Array);// true
console.log(obj.constructor === Object);// true
console.log(fun.constructor === Function);// true
console.log(big.constructor === BigInt);// true
无法判断null、undefined,并且contructor指向可以被告变
- Object.prototype.toString.call(推荐)
console.log(Object.prototype.toString.call(bool));//[object Boolean]
console.log(Object.prototype.toString.call(num));//[object Number]
console.log(Object.prototype.toString.call(str));//[object String]
console.log(Object.prototype.toString.call(und));//[object Undefined]
console.log(Object.prototype.toString.call(nul));//[object Null]
console.log(Object.prototype.toString.call(arr));//[object Array]
console.log(Object.prototype.toString.call(obj));//[object Object]
console.log(Object.prototype.toString.call(fun));//[object Function]
console.log(Object.prototype.toString.call(big));//[object BigInt]
console.log(Object.prototype.toString.call(new Date()));//[object Date]
均可准确判断
# Vue-router的原理,两种模式的区别,history模式如何在服务端做额外配置
单页面应用,指的是应用只有一个主页面,通过动态替换DOM内容并同步修改url地址来模拟多页应用的效果,切换页面的功能直接由前端来完成,无需后端处理。单页面 能模拟多页面应用的效果,主要得益于前端路由机制。
# hash模式
默认的模式,其特征就是在url上有一个#
,比如http://www.example.com/#/test
,其中的hash值是#/test
。他的特点就是值虽然在URL里,但不会包含在http
请求中,无论如何改变都不会对后端产生影响,也就是说hash模式不会重载页面
# history模式
通过mode: history
开启,其特点就是看起来是正常的url,比如http://www.example.com/test
。他主要利用了HTML5 History Interface中新增的pushState()
和replaceState()方法,对浏览器的历史记录栈进行修改。只是当他们进行修改时,并不会立即向后端发送请求。但如果进行了手动刷新,他会向后端发起请求,因此,history
模式需要后端进行配合,将路由重定向到一级页面
# 后台配置
后台需要配置的是,当前端访问一个不存在的路由时,重定向到一级页面,以apache为例 我们用vue-cli建立一个工程后,以history模式打包,将apache的配置指向dist目录启动。
在切换时,用于只是单纯的修改浏览器历史记录,所以并没有什么问题
在about页面进行刷新,则会请求后台,如果后台没有进行相应的配置,则会出现404错误
此时,我们在dist目录下建立一个 .htaccess 文件,内容如下
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
在about页面刷新后,回复正常
# Vue框架开发的优势
flex布局 →