手写new操作符
-
当然了,在手写之前,我们一定要知道new操作符都做了哪些事情,这样有利于理解。
-
它创建了一个全新的对象。 设置原型链,让空对象的__proto__属性,指向函数的Prototype。 它使this指向新创建的对象。并且执行函数体 通过new创建的每个对象将最终被Prototype链接到这个函数的prototype对象上。 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。function myNew(classN, ...arg) { var obj = {}; //var obj=new Object(); obj.__proto__ = classN.prototype; var res = classN.call(obj, ...arg); return typeof res === 'object' ? res : obj; }
手写call
-
将函数设为对象的属性 执行&删除这个函数 指定this到函数并传入给定参数执行函数 如果不传入参数,默认指向 windowFunction.prototype.myCall = function (context, ...arg) { if(typeof context !=='object'&& typeof context !=='function' && context !==null ){ arg.unshift(context) context =window; } var n = Symbol(); context[n] = this; var res = context[n](...arg); delete context[n]; return res; } function f2(a, b) { console.log(this); console.log(a + b); } var obj = { q: 123, w: 456, e: 789 } // f2.myCall(obj, 3, 3);
手写apply
- 实现思路和bind类似,所以在此就不多废话了
Function.prototype.myApply = function (context, arg) {
context = context || window;
var n = Symbol();
context[n] = this;
var res = context[n](...arg);
delete context[n];
return res;
}
function fn(a, b, c, d) {
console.log(this);
console.log(a + b + c + d);
}
var obj = {
a: 1, b: 2, c: 3, d: 5
}
// fn.myApply(obj, [5, 6, 7, 8]);
手写bind
- 返回一个函数,绑定this,传递预置参数
- bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
Function.prototype.myBind = function (context=window, ...arg) { var _this=this; return function(...arr) { return _this.apply(context,arg.concat(arr)) // return _this.apply(context,[...arg,...arr]) } } var fun = function () { console.log(arguments); console.log(this); } // fun.bind(obj, 666, 888);
实现节流throttle
- 将一个函数的调用频率限制在一定时间内,例如 1s,那么 1s 内这个函数一定不会被调用两次
function throttle(fn,wait=1000) { var flag=true; return function () { if(!flag)return; flag=false; setTimeout(() => { flag=true; fn.call(this,...arguments) }, wait); } } function f() { console.log(this); } window.onscroll=throttle(f)
实现防抖debounce
- 当一次事件发生后,事件处理器要等一定的时间,如果这段时间过去后再也没有事件发生,就处理最后一次发生的事件。(典型例子:限制 鼠标连击 触发。)
function debounce(fn,wait) {
wait=wait||100;
var timer=null;
return function () {
clearTimeout(timer);
timer=setTimeout(()=>{
fn.apply(this,arguments)//保证this指向,传递参数
},wait)
}
}
let fn=function () {console.log(666)}
let f=debounce(fn,100,true)
window.onscroll=f;
实现深克隆
- 面试版
//数据初始化
function Person(name) {
this.name = name;
}
const WHO = new Person('who');
const obj = {
a: 1,
b: function (arg) {
console.log('嘤嘤嘤');
},
c: {
d: 3,
e: {
f: [1,[2,[3,[4,[5]]]]],
g: {
h: 5
}
}
},
date: [new Date(1536627600000), new Date(1540047600000)],
reg: new RegExp('\\w+'),
num: [NaN, Infinity, -Infinity],
person: Jack,
};
//深克隆函数
function deepClone(obj) {
if (obj === null) return null;
if (typeof obj !== 'object') return obj;
if (obj.constructor === Date) return new Date(obj);
if (obj.constructor === RegExp) return new RegExp(obj);
const newObj = new obj.constructor (); //保持继承链
for (let key in obj) {
if (obj.hasOwnProperty(key)) { //不遍历其原型链上的属性
const val = obj[key];
newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合。
}
}
return newObj;
}
//判断代码
const obj2 = deepClone(obj);
console.log(obj);
console.log(obj2);
obj.c.e.f = 1000;
obj2.c.e.g.h = 2000;
console.log(obj.c.e.f, obj2.c.e.f);
console.log( obj.c.e.g.h, obj2.c.e.g.h);
-
最简单的操作(弊端)
-
他无法实现对函数 、RegExp等特殊对象的克隆 会抛弃对象的constructor,所有的构造函数会指向Object 对象有循环引用,会报错var newObj = JSON.parse( JSON.stringify( someObj ) )
数组扁平化
- 利用flat
console.log(ary.flat(Infinity));
- 利用tostring
function flatten(ary) {
return ary.toString().split(',').map(item=>+item)
}
console.log(flatten(ary));
- 利用some
function flatten(arr) {
while (arr.some(item=>Array.isArray(item))) {
arr=[].concat.apply([],arr);
}
return arr;
}
console.log(flatten(ary));
- 利用扩展运算符
function flatten(arr) {
while (ary.some(Array.isArray)) {
ary = [].concat(...ary);
}
return ary;
}
console.log(flatten(ary));
- 利用foreach循环把括号去掉
function flat(arr) {
let temp = [];
function fn(ary) {
ary.forEach(item => {
if (typeof item == 'object') {
fn(item)
} else {
temp.push(item)
}
})
}
fn(arr)
return temp;
}
console.log(flat(ary));
实现Instanceof
function myInstance(temp, classN) {
// temp通过__proto__向上查找的时候,若某次的__proto__ === classN.prototype 返回true
// 当某次的__proto__ === null ; 这时返回false;
let str=typeof temp;
if(str !=='object' && str !=='function')return false
var left = temp.__proto__,
right = classN.prototype;
while (left) {
if (left === right) return true;
left = left.__proto__;
}
return false
}
myInstance([], Number) //false
实现JSON.parse
- 直接用eval,极易容易被xss攻击
var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");
// JSON.parse(text[, reviver])
- eval与 Function都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用。
var jsonString = '{ "age": 18, "name": "ying" }'
var json = (new Function('return ' + jsonString))();
实现JSON.stringify
- 主要实现递归调用
// 数据类型判断
function getType(attr) {
let type = Object.prototype.toString.call(attr);
let newType = type.substr(8, type.length - 9);
return newType;
}
// 转换函数
function StringIfy(obj) {
// 如果是非object类型 or null的类型直接返回 原值的String
if (typeof obj !== "object" || getType(obj) === null) {
return String(obj);
}
// 声明一个数组
let json = [];
// 判断当前传入参数是对象还是数组
let arr = obj ? getType(obj) === "Array" : false;
// 循环对象属性
for (let key in obj) {
// 判断属性是否在对象本身上
if (obj.hasOwnProperty(key)) {
// console.log(key, item);
// 获取属性并且判断属性值类型
let item = obj[key];
if (item === obj) {
console.error(new TypeError("Converting circular structure to JSON"));
return false;
}
if (/Symbol|Function|Undefined/.test(getType(item))) {
delete obj[key];
continue;
}
// 如果为object类型递归调用
if (getType(item) === "Object") {
// consoarrle.log(item)
item = StringIfy(item);
}
let IsQueto =
getType(item) === "Number" ||
getType(item) === "Boolean" ||
getType(item) === "Null"
? ""
: '"';
// 拼接数组字段
json.push((arr ? IsQueto : '"' + key + '": "') + String(item) + IsQueto);
}
}
console.log(arr, String(json));
// 转换数组字段为字符串
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
let aa = StringIfy([1, 2, 4]);
let test = {
name: "name",
age: undefined,
func: function() {},
sym: Symbol("setter")
};
let newTest = StringIfy(test);
console.log(aa, newTest);
var firstObj = {
name: "firstObj"
};
firstObj.newKey = firstObj;
StringIfy(firstObj);
实现柯里化函数
function currying(fn, length) {
length = length || fn.length; //传参数的话,就是传进来的参数,没有的话,就是函数的形参个数
return function (...arg) {
if (arg.length >= length) {
// 如果实参的个数 大于等于 传递的参数
return fn.apply(this, arg)
} else {
return currying(fn.bind(this, ...arg), length - arg.length);
// bind 返回新函数体,不执行;
}
}
}
let f3 = function (a, b, c) {
return a + b + c
}
let add = currying(f3);
// currying 柯里化函数
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));
console.log(add(1, 2, 3));
实现类的继承
- 最理想的继承方式
function Parent(name) {
this.parent = name
}
Parent.prototype.say = function() {
console.log(`${this.parent}: 你像ying`)
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.child = name
}
/**
1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
console.log(`${this.parent}好,我是${this.child}`);
}
// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你像ying
var child = new Child('yingge', 'father');
child.say() // father好,我是yingge
实现create创建对象
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
解析URL为对象
- split版
String.prototype.queryParams = function (key) {
let str = this.split('?')[1];
let arr = str.split('&');
let obj = {};
arr.forEach(item => {
let a = item.split('=');
obj[a[0]] = a[1];
})
return key ? obj[key] : obj;
}
let url2 = "http://www.zhufengpeixun.cn/?lx=1&from=wx";
console.log(url2.queryParams("from")); //=>"wx"
- replace版
var str = 'http://www.baidu.com?a=12&b=13&c=34';
function getParam(str) {
var o = {};
str.replace(/([^?=&]+)=([^?=&]+)/g, (a, b, c) => o[b] = c);
return o;
}
var obj = getParam(str);
console.log(obj); //{a: "12", b: "13", c: "34"}
获取cookie
String.prototype.getCookie = function (key) {
var reg=/([^; =]+)=([^; =]+)/;
// console.log(reg.exec(cookie));
var ary=reg.execAll(this);
// console.log(ary);
var obj={};
ary.forEach(item => {
obj[item[1]]=item[2]
});
return key ? obj[key] : obj
}
var cookie =
'BAIDUID=F02A1FE2F5665FB8E3520A8143BAEBE8:FG=1; BIDUPSID=F02A1FE2F5665FB8E3520A8143BAEBE8; PSTM=1555149539; BD_UPN=12314753; sugstore=1; MCITY=-131%3A; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BD_HOME=1; H_PS_PSSID=29634_1432_21125_20698_29522_29518_29720_29568_29221_29589; delPer=0; BD_CK_SAM=1; PSINO=1; H_PS_645EC=2172HsArbSEz1xcCOqM%2BZo9cSofi5X3%2B7EyRbM50a9N%2F%2FvGByg4%2F%2FEpbSsw';
var res1=cookie.getCookie();
var res2=cookie.getCookie('BIDUPSID');
console.log(res1,res2);
实现模板引擎
function render(template,data) {
var str=template.replace(/\{\{(\w+)\}\}/g,function (context,$1) {
return data[$1];
})
return str
}
let template='我是{{name}},年龄{{age}},性别{{sex}}';
let data ={
name:'lili',age:18,sex:'man'
}
console.log (render(template,data))
实现千位分隔符
- 也就是保留三位小数
function millimeter() {
return this.replace(/\d{1,3}(?=(\d{3})+$)/g, content => content + ',');
}
//补充下 怎么将它扩展到字符串的原型上呢?
["millimeter"].forEach(item => {
String.prototype[item] = eval(item);
});
快速排序并去重
- 利用双for
Array.prototype.unique = function () {
for (var i = 0; i < this.length; i++) {
var temp = this[i];
for (var j = i + 1; j < this.length; j++) {
if (temp == this[j]) {
this.splice(j, 1) j--
}
}
}
return this
}
var ary = [1, 2, 1, 2, 1, 2, 1, 555, 55, 8, 1, 2, 8];
ary.unique();
- es6的新增方法set
Array.prototype.unique = function () {
var temp = new Set(this);
return [...temp]
}
var ary = [1, 2, 1, 2, 1, 2, 1, 555, 55, 8, 1, 2, 8];
ary.unique();
- 利用filter
var array = [1, 2, 1, 1, '1'];
function unique(array) {
var res = array.filter(function(item, index, array){
return array.indexOf(item) === index;
})
return res;
}
console.log(unique(array));
- 利用concat
var array = [1, 2, 1, 1, '1'];
function unique(array) {
return array.concat().sort().filter(function(item, index, array){
return !index || item !== array[index - 1]
})
}
console.log(unique(array));
- indexOf
var array = [1, 1, '1'];
function unique(array) {
var res = [];
for (var i = 0, len = array.length; i < len; i++) {
var current = array[i];
if (res.indexOf(current) === -1) {
res.push(current)
}
}
return res;
}
console.log(unique(array));
查找字符串中出现次数最多的字符
- 利用去重思想
let str = "zhufengpeixunzhoulaoshi";
let obj = {};
[].forEach.call(str, char => {
if (typeof obj[char] !== "undefined") {
obj[char]++;
return;
}
obj[char] = 1;
});
let max = 1,
res = [];
for (let key in obj) {
let item = obj[key];
item > max ? max = item : null;
}
for (let key in obj) {
let item = obj[key];
if (item === max) {
res.push(key);
}
}
console.log(`出现次数最多的字符:${res},出现了${max}次`);
- 原生方法,不推荐使用,但是逻辑比较清晰
var str='aijiuxianglantianbaiyunqingkongwanli';
var ary=str.split('').sort((a,b)=>a.localeCompare(b)).join('');
console.log(ary);//aaaaaabgggiiiiiiijkllnnnnnnnoqtuuwxy
var reg=/(\w)\1+/g;
var res=ary.match(reg).sort((a,b)=>b.length-a.length);
console.log(res); // ["iiiiiii", "nnnnnnn", "aaaaaa", "ggg", "ll", "uu"]
var max=res[0].length;
console.log(max); //7
var text=[res[0].substr(0,1)];
console.log(text); //i
for (let i = 1; i < res.length; i++) {
var temp=res[i];
console.log(temp);
if(temp.length<max){
break;
}
text.push(temp.substr(0,1))
}
console.log(`出现次数最多的字符是:${res}, ${max}次`);