js 强化

手写原生js

1. call

Function.prototype.call = function (context) {
    /** 如果第一个参数传入的是 null 或者是 undefined, 那么指向this指向 window/global */
    /** 如果第一个参数传入的不是null或者是undefined, 那么必须是一个对象 */
    if (!context) {
        //context为null或者是undefined
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this; //this指向的是当前的函数(Function的实例)(bar函数)
    let rest = [...arguments].slice(1);//获取除了this指向对象以外的参数, 空数组slice后返回的仍然是空数组
    let result = context.fn(...rest); //隐式绑定,当前函数的this指向了context.考虑到bar可能返回一个对象
    delete context.fn;
    return result;
}

//测试代码
var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
    console.log(this.name);
    console.log(job, age);
}
bar.call(foo, 'programmer', 20);
// Selina programmer 20
bar.call(null, 'teacher', 25);
// 浏览器环境: Chirs teacher 25; node 环境: undefined teacher 25

复制代码

2.apply

Function.prototype.apply = function (context, rest) {
    if (!context) {
        //context为null或者是undefined时,设置默认值
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    let result;
    if(rest === undefined || rest === null) {
        //undefined 或者 是 null 不是 Iterator 对象,不能被 ...
        result = context.fn(rest);
    }else if(typeof rest === 'object') {
        result = context.fn(...rest);
    }
    delete context.fn;
    return result;
}
var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar(job, age) {
    console.log(this.name);
    console.log(job, age);
}
bar.apply(foo, ['programmer', 20]);
// Selina programmer 20
bar.apply(null, ['teacher', 25]);
// 浏览器环境: Chirs programmer 20; node 环境: undefined teacher 25
复制代码

3.bind

bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

Function.prototype.bind = function(context) {
    if(typeof this !== "function"){
       throw new TypeError("not a function");
    }
    let self = this;
    let args = [...arguments].slice(1);
    function Fn() {};
    Fn.prototype = this.prototype;
    let bound = function() {
        let res = [...args, ...arguments]; //bind传递的参数和函数调用时传递的参数拼接
        context = this instanceof Fn ? this : context || this;
        return self.apply(context, res);
    }
    //原型链
    bound.prototype = new Fn();
    return bound;
}

var name = 'Jack';
function person(age, job, gender){
    console.log(this.name , age, job, gender);
}
var Yve = {name : 'Yvette'};
let result = person.bind(Yve, 22, 'enginner')('female');
复制代码

4.跨域之原生js

4.1 jsonp

//前端代码
function jsonp({url, params, cb}) {
    return new Promise((resolve, reject) => {
        //创建script标签
        let script = document.createElement('script');
        //将回调函数挂在 window 上
        window[cb] = function(data) {
            resolve(data);
            //代码执行后,删除插入的script标签
            document.body.removeChild(script);
        }
        //回调函数加在请求地址上
        params = {...params, cb} //wb=b&cb=show
        let arrs = [];
        for(let key in params) {
            arrs.push(`${key}=${params[key]}`);
        }
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
    });
}
//使用
function sayHi(data) {
    console.log(data);
}
jsonp({
    url: 'http://localhost:3000/say',
    params: {
        //code
    },
    cb: 'sayHi'
}).then(data => {
    console.log(data);
});

//express启动一个后台服务
let express = require('express');
let app = express();

app.get('/say', (req, res) => {
    let {cb} = req.query; //获取传来的callback函数名,cb是key
    res.send(`${cb}('Hello!')`);
});
app.listen(3000);
复制代码

4.2 websocket

Websocket 不受同源策略影响,只要服务器端支持,无需任何配置就支持跨域。

前端页面在 8080 的端口:
let socket = new WebSocket('ws://localhost:3000'); //协议是ws
socket.onopen = function() {
    socket.send('Hi,你好');
}
socket.onmessage = function(e) {
    console.log(e.data)
}

服务端 3000端口。可以看出websocket无需做跨域配置
let WebSocket = require('ws');
let wss = new WebSocket.Server({port: 3000});
wss.on('connection', function(ws) {
    ws.on('message', function(data) {
        console.log(data); //接受到页面发来的消息'Hi,你好'
        ws.send('Hi'); //向页面发送消息
    });
});
复制代码

5.对象键值

var a = {},
  b = '123',
  c = 123;
a[b] = 'b';
a[c] = 'c';
for(var key in a){
  console.log(typeof key); //string
  
}


var a = {},
  b = {
    key: '123'
  },
  c = {
    key: '456'
  };
  
  a[b] = 'b';
  a[c] = 'c';

  console.log(a); //{ '[object Object]': 'c' }

  console.log(a[b]);//c

复制代码

6.立即执行函数

var a = 1;
(function a() {
   a=2;
  console.log(a);//function a(){}
})()
 console.log(a); // 外面访问不到里面的 a值,1,相当于自己有一个形参.当变量和函数名重名,优先函数名

function a() {
  a = 2;
  console.log(a); //2
}
a();
复制代码

7.数组偏平化(降维)

var arr = [
  [1, 2, 2],
  [3, 4, 5, 5],
  [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];

function flatten(arr) {
  while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}
console.log(flatten(arr));

console.log(arr.toString()) //'1,2,2,3,4,5,5,6,7,8,9,11,12,12,13,14,10'
//全部一级化成字符串,不含嵌套包含

var b=arr.toString().split(',').sort((a,b)=>a-b).map(Number)

console.log([...new Set(b)]); //[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ]

function spreadArr(arr = []) {
  if (arr.some(ele => Array.isArray(ele))) {
    let newArr = [];
    arr.forEach((ele) => {
      if (Array.isArray(ele)) {
        newArr = newArr.concat(...ele)
      } else {
        if (!newArr.includes(ele))
          newArr.push(ele)
      }
    })
    return spreadArr(newArr);
  }
  return arr.sort((a, b) => a - b);
}
spreadArr([1,[2,[3,4]]]);
复制代码

8. 静态词法作用域

// 作用域链在定义时而不是执行时
    a=2;
    function A() {
      let a = 1
      B = function () {
        console.log(a)
      }
    }
    A()
    B() // 1 静态作用域


    a = 2;
    B = function () {
      console.log(a)
    }
    function A() {
      let a = 1
      B()
    }
    A() //2

    扩展this
    setTimeout(function () {
        console.log(this); /*window*/
      }, 0);
     var b = {
      id: 'b',
      say:function(){
        console.log(this);
      }
    };
    var c = {
      f: function () {
        console.log(this)
      }
    }
    c.f.call(b); /*b-> {id: "b", say: ƒ}*/
复制代码

9. 防抖和节流

1.防抖
 function debounce(fn) {
        let timeout = null; // 创建一个标记用来存放定时器的返回值
        return function () {
          clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
          timeout = setTimeout(() => {
            // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内 不管现在是否执行setTimeout()函数,都会有一个返回值 timeout
            // 如果还有字符输入的话,就不会执行 fn 函数
            fn.apply(this);//this为input
            console.log(this+'哈哈哈哈哈,防抖里面执行操作');
          }, 500);
          console.log(timeout);//正常执行
          console.log('正常');//正常执行
        };
      }
      function sayHi() {
        console.log('防抖成功'+this.value);
      }
      var inp = document.getElementById('inp');
      inp.addEventListener('input', debounce(sayHi));
  2. 节流
        function throttle(fn) {
          let canRun = true; // 通过闭包保存一个标记
          return function () {
            if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return// 可以往下执行 canRun=true
            canRun = false; // 立即设置为false
            setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
              fn.apply(this, arguments);
              // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
              //当定时器没有执行的时候标记永远是false,在开头被return掉
              canRun = true;//就为了将事件频率限制在一定范围内
            }, 3000);
          };
        }
        function sayHi(e) {
          // e.target是触发冒泡事件的对象
          console.log(e,e.target);
        }
        window.addEventListener('click', throttle(sayHi));
复制代码
复制代码
复制代码

转载于:https://juejin.im/post/5ce3be34e51d454fbf54094d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值