# 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.comb.example.com
实现方法就是把两个网站的document.domain设置为example.com

# postMessage

window.postMessage() 方法可以安全地实现跨源通信。
如果不想发给别人或者,这设定好targetOrigin
如果不想接受其他人的信息,则在监听message时间时,使用originsource属性验证发件人的身份

# cors(主流)

服务端设置Access-Control-Allow-Origin即可。 如需传递cookie,后台需要设置Access-Control-Allow-Credentialstrue,前端要在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目录启动。

在切换时,用于只是单纯的修改浏览器历史记录,所以并没有什么问题 history模式

在about页面进行刷新,则会请求后台,如果后台没有进行相应的配置,则会出现404错误 history模式

此时,我们在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页面刷新后,回复正常 history模式

# Vue框架开发的优势