手写原生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));
复制代码
复制代码
复制代码