JavaScript手写代码无敌秘籍

转自JavaScript手写代码无敌秘籍

转自面试题锦(大厂面试前夕的挣扎)

【前端面试】同学,你会手写代码吗?


手写路径导航

  • 实现一个new操作符
  • 实现一个JSON.stringify
  • 实现一个JSON.parse
  • 实现一个call或 apply
  • 实现一个Function.bind
  • 实现一个继承
  • 实现一个JS函数柯里化
  • 手写一个Promise(中高级必考)
  • 手写防抖(Debouncing)和节流(Throttling)
  • 手写一个JS深拷贝
  • 实现一个instanceOf

1. 实现一个new操作符

来源:「你不知道的javascript」 英文版

new操作符做了这些事:

  • 它创建了一个全新的对象。
  • 它会被执行[[Prototype]](也就是__proto__)链接。
  • 它使this指向新创建的对象。。
  • 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
  • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。
    function New(func) {
        var res = {};
        if (func.prototype !== null) {
            res.__proto__ = func.prototype;
        }
        var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
        if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
            return ret;
        }
        return res;
    }
    var obj = New(A, 1, 2);
    // equals to
    var obj = new A(1, 2);
复制代码

2. 实现一个JSON.stringify

JSON.stringify(value[, replacer [, space]])

  • Boolean | Number| String 类型会自动转换成对应的原始值。
  • undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
  • 不可枚举的属性会被忽略
  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
    function jsonStringify(obj) {
        lettype = typeof obj;
        if (type !== "object" || type === null) {
            if (/string|undefined|function/.test(type)) {
                obj = '"' + obj + '"';
            }
            return String(obj);
        } else {
            let json = []
            arr = (obj && obj.constructor === Array);
            for (let k in obj) {
                let v = obj[k];
                lettype = typeof v;
                if (/string|undefined|function/.test(type)) {
                    v = '"' + v + '"';
                } elseif (type === "object") {
                    v = jsonStringify(v);
                }
                json.push((arr ? "" : '"' + k + '":') + String(v));
            }
            return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
        }
    }
    jsonStringify({x : 5}) // "{"x":5}"
    jsonStringify([1, "false", false]) // "[1,"false",false]"
    jsonStringify({b: undefined}) // "{"b":"undefined"}"
复制代码

3. 实现一个JSON.parse

JSON.parse(text[, reviver])

用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。

3.1 第一种:直接调用 eval

    function jsonParse(opt) {
        returneval('(' + opt + ')');
    }
    jsonParse(jsonStringify({x : 5}))
    // Object { x: 5}
    jsonParse(jsonStringify([1, "false", false]))
    // [1, "false", falsr]
    jsonParse(jsonStringify({b: undefined}))
    // Object { b: "undefined"}
复制代码

避免在不必要的情况下使用 eval,eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。

它会执行JS代码,有XSS漏洞。

如果你只想记这个方法,就得对参数json做校验。

    var rx_one = /^[\],:{}\s]*$/;
    var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
    if (
        rx_one.test(
            json
                .replace(rx_two, "@")
                .replace(rx_three, "]")
                .replace(rx_four, "")
        )
    ) {
        var obj = eval("(" +json + ")");
    }
复制代码

3.2 第二种:Function

来源 神奇的eval()与new Function()

核心:Functioneval有相同的字符串参数特性。

var func = new Function(arg1, arg2, ..., functionBody);

在转换JSON的实际应用中,只需要这么做。

    var jsonStr = '{ "age": 20, "name": "jack" }'
    var json = (new Function('return ' + jsonStr))();
复制代码

evalFunction 都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用。

这里是面向面试编程,写这两种就够了。至于第三,第四种,涉及到繁琐的递归和状态机相关原理,具体可以看:

《JSON.parse 三种实现方式》

4. 实现一个callapply

call语法:

fun.call(thisArg, arg1, arg2, ...),调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。

apply语法:

func.apply(thisArg, [argsArray]),调用一个函数,以及作为一个数组(或类似数组对象)提供的参数。

4.1 Function.call按套路实现

call核心:

  • 将函数设为对象的属性
  • 执行&删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window

为啥说是套路实现呢?因为真实面试中,面试官很喜欢让你逐步地往深考虑,这时候你可以反套路他,先写个简单版的:

4.1.1 简单版

    var foo = {
        value: 1,
        bar: function() {
            console.log(this.value)
        }
    }
    foo.bar() // 1
复制代码

4.1.2 完善版

当面试官有进一步的发问,或者此时你可以假装思考一下。然后写出以下版本:

    Function.prototype.call2 = function(content = window) {
        content.fn = this;
        let args = [...arguments].slice(1);
        let result = content.fn(...args);
        delect content.fn;
        return result;
    }
    var foo = {
        value: 1
    }
    function bar(name, age) {
        console.log(name)
        console.log(age)
        console.log(this.value);
    }
    bar.call2(foo, 'black', '18') // black 18 1
复制代码

4.2 Function.apply的模拟实现

apply()的实现和call()类似,只是参数形式不同。直接贴代码吧:

    Function.prototype.apply2 = function(context = window) {
        context.fn = this
        let result;
        // 判断是否有第二个参数
        if(arguments[1]) {
            result = context.fn(...arguments[1])
        } else {
            result = context.fn()
        }
        delete context.fn()
        return result
    }
复制代码

5. 实现一个Function.bind()

bind()方法:

会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

此外,bind实现需要考虑实例化后对原型链的影响。

    Function.prototype.bind2 = function(content) {
        if(typeof this != "function") {
            throw Error("not a function")
        }
        // 若没问参数类型则从这开始写
        let fn = this;
        let args = [...arguments].slice(1);
        
        let resFn = function() {
            return fn.apply(this instanceof resFn ? this : content,args.concat(...arguments) )
        }
        functiontmp() {}
        tmp.prototype = this.prototype;
        resFn.prototype = new tmp();
        
        return resFn;
    }
复制代码

6. 实现一个继承

寄生组合式继承

一般只建议写这种,因为其它方式的继承会在一次实例中调用两次父类的构造函数或有其它缺点。

核心实现是:用一个 F 空的构造函数去取代执行了 Parent 这个构造函数。

    function Parent(name) {
        this.name = name;
    }
    Parent.prototype.sayName = function() {
        console.log('parent name:', this.name);
    }
    function Child(name, parentName) {
        Parent.call(this, parentName);  
        this.name = name;    
    }
    function create(proto) {
        functionF(){}
        F.prototype = proto;
        return new F();
    }
    Child.prototype = create(Parent.prototype);
    Child.prototype.sayName = function() {
        console.log('child name:', this.name);
    }
    Child.prototype.constructor = Child;
    
    var parent = new Parent('father');
    parent.sayName();    // parent name: father
    
    var child = new Child('son', 'father');
复制代码

7. 实现一个JS函数柯里化

什么是柯里化?

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

7.1 通用版

    functioncurry() {
        var args = Array.prototype.slice.call(arguments);
    	var fn = function() {
    		var newArgs = args.concat(Array.prototype.slice.call(arguments));
            return multi.apply(this, newArgs);
        }
        fn.toString = function() {
            return args.reduce(function(a, b) {
                return a * b;
            })
        }
        return fn;
    }
    function multiFn(a, b, c) {
        return a * b * c;
    }
    
    var multi = curry(multiFn);
    
    multi(2)(3)(4);
    multi(2,3,4);
    multi(2)(3,4);
    multi(2,3)(4);
复制代码

7.2 ES6骚写法

    const curry = (fn, arr = []) => (...args) => (
      arg => arg.length === fn.length
        ? fn(...arg)
        : curry(fn, arg)
    )([...arr, ...args])
    
    let curryTest=curry((a,b,c,d)=>a+b+c+d)
    curryTest(1,2,3)(4) //返回10
    curryTest(1,2)(4)(3) //返回10
    curryTest(1,2)(3,4) //返回10
复制代码

8. 手写一个Promise(中高级必考)

我们来过一遍Promise/A+规范:

  • 三种状态pending| fulfilled(resolved) | rejected
  • 当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态
  • 当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。
  1. 必须有一个then异步执行方法,then接受两个参数且必须返回一个promise:
    // onFulfilled 用来接收promise成功的值
    // onRejected 用来接收promise失败的原因
    promise1=promise.then(onFulfilled, onRejected);
复制代码

8.1 Promise的流程图分析

来回顾下 Promise用法:

    var promise = new Promise((resolve,reject) => {
        if (操作成功) {
            resolve(value)
        } else {
            reject(error)
        }
    })
    promise.then(function (value) {
        // success
    },function (value) {
        // failure
    })
复制代码

8.2 面试够用版

来源:实现一个完美符合Promise/A+规范的Promise

    function myPromise(constructor){
        let self=this;
        self.status="pending" //定义状态改变前的初始状态
        self.value=undefined;//定义状态为resolved的时候的状态
        self.reason=undefined;//定义状态为rejected的时候的状态
        function resolve(value){
            //两个==="pending",保证了状态的改变是不可逆的
           if(self.status==="pending"){
              self.value=value;
              self.status="resolved";
           }
        }
        function reject(reason){
            //两个==="pending",保证了状态的改变是不可逆的
           if(self.status==="pending"){
              self.reason=reason;
              self.status="rejected";
           }
        }
        //捕获构造异常
        try{
           constructor(resolve,reject);
        }catch(e){
           reject(e);
        }
    }
复制代码

同时,需要在myPromise的原型上定义链式调用的then方法:

    myPromise.prototype.then=function(onFullfilled,onRejected){
       let self=this;
       switch(self.status){
          case"resolved":
            onFullfilled(self.value);
            break;
          case"rejected":
            onRejected(self.reason);
            break;
          default:       
       }
    }
复制代码

测试一下:

    var p=new myPromise(function(resolve,reject){resolve(1)});
    p.then(function(x){console.log(x)})
    //输出1
复制代码

8.3 大厂专供版

直接贴出来吧,这个版本还算好理解

    const PENDING = "pending";
    const FULFILLED = "fulfilled";
    const REJECTED = "rejected";
    
    function Promise(excutor) {
        let that = this; // 缓存当前promise实例对象
        that.status = PENDING; // 初始状态
        that.value = undefined; // fulfilled状态时 返回的信息
        that.reason = undefined; // rejected状态时 拒绝的原因
        that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
        that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数
    
        function resolve(value) { // value成功态时接收的终值
            if(value instanceof Promise) {
                return value.then(resolve, reject);
            }
            // 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
            setTimeout(() => {
                // 调用resolve 回调对应onFulfilled函数
                if (that.status === PENDING) {
                    // 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)
                    that.status = FULFILLED;
                    that.value = value;
                    that.onFulfilledCallbacks.forEach(cb => cb(that.value));
                }
            });
        }
        function reject(reason) { // reason失败态时接收的拒因
            setTimeout(() => {
                // 调用reject 回调对应onRejected函数
                if (that.status === PENDING) {
                    // 只能由pending状态 => rejected状态 (避免调用多次resolve reject)
                    that.status = REJECTED;
                    that.reason = reason;
                    that.onRejectedCallbacks.forEach(cb => cb(that.reason));
                }
            });
        }
    
        // 捕获在excutor执行器中抛出的异常
        // new Promise((resolve, reject) => {
        //     throw new Error('error in excutor')
        // })
        try {
            excutor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    
    Promise.prototype.then = function(onFulfilled, onRejected) {
        const that = this;
        let newPromise;
        // 处理参数默认值 保证参数后续能够继续执行
        onFulfilled =
            typeof onFulfilled === "function" ? onFulfilled : value => value;
        onRejected =
            typeof onRejected === "function" ? onRejected : reason => {
                throw reason;
            };
        if (that.status === FULFILLED) { // 成功态
            return newPromise = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try{
                        let x = onFulfilled(that.value);
                        resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
                    } catch(e) {
                        reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
                    }
                });
            })
        }
    
        if (that.status === REJECTED) { // 失败态
            return newPromise = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onRejected(that.reason);
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            });
        }
    
        if (that.status === PENDING) { // 等待态
            // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
            return newPromise = new Promise((resolve, reject) => {
                that.onFulfilledCallbacks.push((value) => {
                    try {
                        let x = onFulfilled(value);
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
                that.onRejectedCallbacks.push((reason) => {
                    try {
                        let x = onRejected(reason);
                        resolvePromise(newPromise, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            });
        }
    };
复制代码

emmm,我还是乖乖地写回进阶版吧。

9. 手写防抖(Debouncing)和节流(Throttling)

scroll 事件本身会触发页面的重新渲染,同时 scroll 事件的 handler 又会被高频度的触发, 因此事件的 handler 内部不应该有复杂操作,例如 DOM 操作就不应该放在事件处理中。 针对此类高频度触发事件问题(例如页面 scroll ,屏幕 resize,监听用户输入等),有两种常用的解决方法,防抖和节流。

9.1 防抖(Debouncing)实现

典型例子:限制 鼠标连击 触发。

一个比较好的解释是:

当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。

    // 防抖动函数
    function debounce(fn,wait=50,immediate) {
        let timer;
        returnfunction() {
            if(immediate) {
                fn.apply(this,arguments)
            }
            if(timer) clearTimeout(timer)
            timer = setTimeout(()=> {
                fn.apply(this,arguments)
            },wait)
        }
    }
复制代码

9.2 节流(Throttling)实现

可以理解为事件在一个管道中传输,加上这个节流阀以后,事件的流速就会减慢。实际上这个函数的作用就是如此,它可以将一个函数的调用频率限制在一定阈值内,例如 1s,那么 1s 内这个函数一定不会被调用两次

简单的节流函数:

    function throttle(fn, wait) {
    	let prev = new Date();
    	returnfunction() { 
    	    const args = arguments;
    		const now = new Date();
    		if (now - prev > wait) {
    			fn.apply(this, args);
    			prev = new Date();
    		}
    	}
复制代码

9.3 结合实践

通过第三个参数来切换模式。

    const throttle = function(fn, delay, isDebounce) {
      let timer
      let lastCall = 0
      returnfunction (...args) {
        if (isDebounce) {
          if (timer) clearTimeout(timer)
          timer = setTimeout(() => {
            fn(...args)
          }, delay)
        } else {
          const now = new Date().getTime()
          if (now - lastCall < delay) return
          lastCall = now
          fn(...args)
        }
      }
    }
复制代码

10. 手写一个JS深拷贝

有个最著名的乞丐版实现,在《你不知道的JavaScript(上)》里也有提及:

10.1 乞丐版

     var newObj = JSON.parse( JSON.stringify( someObj ) );
    复制代码

### 10.2 面试够用版

    function deepCopy(obj){
        //判断是否是简单数据类型,
        if(typeof obj == "object"){
            //复杂数据类型
            var result = obj.constructor == Array ? [] : {};
            for(let i in obj){
                result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
            }
        }else {
            //简单数据类型 直接 == 赋值
            var result = obj;
        }
        return result;
    }
复制代码

关于深拷贝的讨论天天有,这里就贴两种吧,毕竟我...

11.实现一个instanceOf

    function instanceOf(left,right) {
    
        let proto = left.__proto__;
        let prototype = right.prototype
        while(true) {
            if(proto === null) returnfalseif(proto === prototype) returntrue
            proto = proto.__proto__;
        }
    }
复制代码

欢迎评论区纠错完善

css部分

实现三栏布局

(两侧定宽,中间自适应)

  1. 采用了 absolute,导致父元素脱离了文档流,那所有的子元素也需要脱离文档流。如果页面复杂,那开发的难度可想而知

  2. 利用浮动 当中间内容高于两侧时,两侧高度不会随中间内容变高而变高

  3. 弹性盒子布局

  4. 利用负边距和浮动,实现起来比较复杂

  5. 利用网格布局

    .container { display: grid; grid-template-columns: 100px auto 200px; } 复制代码

BFC(块级格式化上下文)

  • BFC 的原理 其实也就是 BFC 的渲染规则(能说出以下四点就够了)。包括:
  1. BFC 内部的子元素,在垂直方向,边距会发生重叠。
  2. BFC在页面中是独立的容器,外面的元素不会影响里面的元素,反之亦然。
  3. BFC区域不与旁边的float box区域重叠。(可以用来清除浮动带来的影响)。
  4. 计算BFC的高度时,浮动的子元素也参与计算。
  • 如何生成BFC
  • 方法1:overflow: 不为vidible,可以让属性是 hidden、auto。【最常用】
  • 方法2:浮动中:float的属性值不为none。意思是,只要设置了浮动,当前元素就创建了BFC。
  • 方法3:定位中:只要posiiton的值不是 static或者是relative即可,可以是absolute或fixed,也就生成了一个BFC。
  • 方法4:display为inline-block, table-cell, table-caption, flex, inline-flex

flex(面试常问,略)

js部分

call, apply, bind区别? 怎么实现call,apply方法

js继承,构造函数,原型链,构造函数、原型链组合式继承,寄生式组合继承,Object.create polyfill;

数组去重

[...new Set(arr]
复制代码

var arr = [1,2,1,2,3,5,4,5,3,4,4,4,4],
    init=[]
var result = arr.sort().reduce((init, current)=>{
    console.log(init,current)
    if(init.length===0 || init[init.length-1]!==current){
        init.push(current);
    }
    return init;
}, []);
console.log(result);//1,2,3,4,5复制代码
复制代码

防抖节流

var deBounce=function(fn,wait=300){
    let timer
    returnfunction(){
      if(timer){
          clearTimeOut(timer)
      }
      timer=setTimeOut(()=>{
          fn.apply(this,arguments)
      },wait)
    } 
}
var throttle=function(fn,wait=300){
    let prev=+newDate();
    returnfunction(){
        const args=argument,
            now=+newDate();
            if(now>last+wait){
                last=now;
                fn.apply(this,args)
            } 
    }
}
复制代码
复制代码

实现Promise思路

//0 pending , 1 resolve,2 reject functionPromise(fn) {
        ...
        this._state = 0// 状态标记
        doResolve(fn, this)
    }
    
    functiondoResolve(fn, self) {
        var done = false// 保证只执行一个监听try {
            fn(function(value) {
                if (done) return
                done = true
                resolve(self, value)
            },
            function(reason) {
                if (done) return;
                done = true
                reject(self, value)
            })
        } catch(err) {
            if (done) return
            done = true
            reject(self, err)
        }
    }
    
    functionresolve(self, newValue) {
        try {
            self._state = 1;
            ...
        }
        catch(err) {
            reject(self, err)
        }
    }
    
    functionreject(self, newValue) {
        self._state = 2;
        ...
        if (!self._handled) {
            Promise._unhandledRejectionFn(self._value);
        }
    }
复制代码
复制代码

正则实现千位分隔符

functioncommafy(num) {
        return num && num
            .toString()
            .replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
                return $1 + ",";
            });
    }
 console.log(commafy(1312567.903000))
复制代码
复制代码

js事件循环

javascript是单线程语言,任务设计成了两类,同步任务和异步任务 同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,回去了Event Queue读取对应的函数,进入主线程。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。 但是,JS异步还有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入event queue,然后再执行微任务,将微任务放入eventqueue,但是,这两个queue不是一个queue。当你往外拿的时候先从微任务里拿这个回调函数,然后再从宏任务的queue拿宏任务的回调函数 宏任务一般包括:整体代码script,setTimeout,setInterval。 微任务:Promise,process.nextTick

事件流机制,事件委托 event.targe和event.currentTarget的区别

  1. 事件捕获,
  2. 处于目标阶段,
  3. 事件冒泡阶段 event.target返回触发事件的元素 event.currentTarget返回绑定事件的元素

new的过程以及实现new

functioncreate(){
   //1.创建一个空对象let obj={}
   //2.获取构造函数let Con=[].shift.call(arguments)
   //3.设置空对象的原型
   obj._proto_=Con.prototype
   //4.绑定this并执行构造函数,给新对象添加属性和方法let result=Con.apply(obj,arguments)
   //5.确保返回值为对象return result instanceofObject?result:obj
}
复制代码
复制代码

前端路由的两种实现原理

  1. Hash模式
  • window对象提供了onhashchange事件来监听hash值的改变,一旦url中的hash值发生改变,便会触发该事件。
  1. History 模式
  • popstate监听历史栈信息变化,变化时重新渲染
  • 使用pushState方法实现添加功能
  • 使用replaceState实现替换功能

封装ajax

/* 封装ajax函数
 * @param {string}opt.type http连接的方式,包括POST和GET两种方式
 * @param {string}opt.url 发送请求的url
 * @param {boolean}opt.async 是否为异步请求,true为异步的,false为同步的
 * @param {object}opt.data 发送的参数,格式为对象类型
 * @param {function}opt.success ajax发送并接收成功调用的回调函数
 */functionmyAjax(opt){
     opt = opt || {};
     opt.method = opt.method.toUpperCase() || 'POST';
     opt.url = opt.url || '';
     opt.async = opt.async || true;
     opt.data = opt.data || null;
     opt.success = opt.success || function () {}
     let xmlHttp = null;
     if (XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
     }else{
         xmlHttp =new ActiveXObject('Microsoft.XMLHTTP')
     }
     let params;
    for (var key in opt.data){
        params.push(key + '=' + opt.data[key]);
    }
    let postData = params.join('&');
    if (opt.method.toUpperCase() === 'POST') {
        xmlHttp.open(opt.method, opt.url, opt.async);
        xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
        xmlHttp.send(postData);
    }elseif (opt.method.toUpperCase() === 'GET') {
        xmlHttp.open(opt.method, opt.url + '?' + postData, opt.async);
        xmlHttp.send(null);
    } 
     xmlHttp.onreadystatechange= function () {
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                opt.success(xmlHttp.responseText);//如果是json数据可以在这使用opt.success(JSON.parse( xmlHttp.responseText))
            }
     };
}
复制代码
复制代码

url拿参数

var url = "http://www.taobao.com/index.php?key0=0&key1=1&key2=2";
functionparseQueryString(url){
    var str = url.split("?")[1],    //通过?得到一个数组,取?后面的参数
        items = str.split("&");    //分割成数组var arr,name,value;

    for(var i=0; i复制代码

HTTP部分

http协议

HTTP协议(超文本传输协议) 主要特点

  1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  2. 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  3. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  4. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
  5. 支持B/S及C/S模式。

HTTP之请求消息Request

  1. 请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
  2. 请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本.
  3. 请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
  4. 空行,请求头部后面的空行是必须的
  5. 请求数据也叫主体,可以添加任意的其他数据。

HTTP之响应消息Response

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

  1. 状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
  2. 消息报头,用来说明客户端要使用的一些附加信息
  3. 第三部分:空行,消息报头后面的空行是必须的
  4. 第四部分:响应正文,服务器返回给客户端的文本信息。

在浏览器地址栏键入URL,按下回车之后会经历以下流程:

  1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  2. 建立TCP连接;
  3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
  4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
  5. 释放 TCP连接(四次挥手);
  6. 浏览器将该 html 文本并显示内容;

三次握手

SYN (同步序列编号)ACK(确认字符)

  1. 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
  2. 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  3. 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

四次挥手

  1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
  4. 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

网页生成的过程,大致可以分为五步:

  1. html代码转化为dom
  2. css代码转化为cssom
  3. 结合dom和cssom,生成一颗渲染树
  4. 生成布局layout,即将所有的渲染树的节点进行平面合成
  5. 将布局绘制paint在屏幕上(可以拓展讲一下减少浏览器渲染的重排和重绘)

浏览器缓存

当浏览器再次访问一个已经访问过的资源时,它会这样做:

  1. 看看是否命中强缓存,如果命中,就直接使用缓存了。
  2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。
  3. 如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
  4. 否则,返回最新的资源。

vue

vue Virtual DOM

其实 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。 Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。那么在 Vue.js 中,VNode 的 create 是通过之前提到的 createElement 方法创建的,我们接下来分析这部分的实现。

vue的响应式原理

  • Object.defineProperty(obj, prop, descriptor)

  • obj 是要在其上定义属性的对象;prop 是要定义或修改的属性的名称;descriptor 是将被定义或修改的属性描述符。 比较核心的是 descriptor,它有很多可选键值,具体的可以去参阅它的文档。这里我们最关心的是 get 和 set,get 是一个给属性提供的 getter 方法,当我们访问了该属性的时候会触发 getter 方法;set 是一个给属性提供的 setter 方法,当我们对该属性做修改的时候会触发 setter 方法。一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象

  • 对象递归调用

  • 数组变异方法的解决方法:代理原型/实例方法

  • observe

  • observe 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer,如果已经添加过则直接返回,否则在满足一定条件下去实例化一个 Observer 对象实例。

  • observe 的功能就是用来监测数据的变化.

  • Observer 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新:

  • 依赖收集和派发更新

  • 收集依赖的目的是为了当这些响应式数据发生变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者去做相应的逻辑处理,我们把这个过程叫派发更新,其实 Watcher 和 Dep 就是一个非常经典的观察者设计模式的实现

  • 派发更新就是数据发生变化的时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher,都触发它们的 update 过程,这个过程又利用了队列做了进一步优化,在 nextTick 后执行所有 watcher 的 run,最后执行它们的回调函数

  • vue编译Compile的过程主要分以下几步 parse(生成AST)=> optimize(优化静态节点) => generate(生成render function)

    // 解析模板字符串生成 ASTconst ast = parse(template.trim(), options) //优化语法树 optimize(ast, options) //生成代码const code = generate(ast, options) 复制代码

对vuex的理解,单向数据流

前端安全

XSS和CSRF

  • XSS:跨站脚本攻击,是一种网站应用程序的安全漏洞攻击,是代码注入的一种。常见方式是将恶意代码注入合法代码里隐藏起来,再诱发恶意代码,从而进行各种各样的非法活动。

预防:

使用XSS Filter

  1. 输入过滤,对用户提交的数据进行有效性验证,仅接受指定长度范围内并符合我们期望格式的的内容提交,阻止或者忽略除此外的其他任何数据。
  2. 输出转义,当需要将一个字符串输出到Web网页时,同时又不确定这个字符串中是否包括XSS特殊字符,为了确保输出内容的完整性和正确性,输出HTML属性时可以使用HTML转义编码(HTMLEncode)进行处理,输出到复制代码 复制代码

    路由实现 - history

    history 路由
    首页个人中心页帮助页
    复制代码 复制代码

    还有一些稍复杂的可以写,有时间再补。

你可能感兴趣的:(JavaScript手写代码无敌秘籍)