能够手撕各种JavaScript原生函数,可以说是进大厂必备!同时对JavaScript源码的学习和实现也能帮助我们快速扎实地提升自己的前端编程能力。
最近很多人和我一样在积极地准备前端面试笔试,所以就整理了一些前端面试笔试中非常容易被问到的原生函数实现和各种前端原理实现,其中部分源码戳这里。
我们首先知道new做了什么:
知道new做了什么,接下来我们就来实现它
function create(Con, ...args){
// 创建一个空的对象
this.obj={};
// 将空对象指向构造函数的原型链
Object.setPrototypeOf(this.obj, Con.prototype);
// obj绑定到构造函数上,便可以访问构造函数中的属性,即this.obj.Con(args)
let result=Con.apply(this.obj, args);
// 如果返回的result是一个对象则返回
// new方法失效,否则返回obj
return result instanceof Object ? result : this.obj;
}
思路很简单,就是利用Object.prototype.toString
Array.myIsArray=function(o) {
return Object.prototype.toString.call(Object(o))==='[object Array]';
};
function create=function (o) {
var F=function () {};
F.prototype=o;
return new F();
};
真实经历,最近在字节跳动的面试中就被面试官问到了,要求手写实现一个简单的Event类。
class Event {
constructor () {
// 储存事件的数据结构
// 为查找迅速, 使用对象(字典)
this._cache={}
}
// 绑定
on(type, callback) {
// 为了按类查找方便和节省空间
// 将同一类型事件放到一个数组中
// 这里的数组是队列, 遵循先进先出
// 即新绑定的事件先触发
let fns=(this._cache[type]=this._cache[type] || [])
if(fns.indexOf(callback)===-1) {
fns.push(callback)
}
return this
}
// 解绑
off (type, callback) {
let fns=this._cache[type]
if(Array.isArray(fns)) {
if(callback) {
let index=fns.indexOf(callback)
if(index !==-1) {
fns.splice(index, 1)
}
} else {
// 全部清空
fns.length=0
}
}
return this
}
// 触发emit
trigger(type, data) {
let fns=this._cache[type]
if(Array.isArray(fns)) {
fns.forEach((fn)=> {
fn(data)
})
}
return this
}
// 一次性绑定
once(type, callback) {
let wrapFun=()=> {
callback.call(this);
this.off(type, wrapFun); // 执行完以后立即解绑
};
this.on(type, wrapFun); // 绑定
return this;
}
}
let e=new Event()
e.on('click',function(){
console.log('on')
})
// e.trigger('click', '666')
console.log(e)
先回忆一下Array.prototype.reduce语法:
Array.prototype.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
然后就可以动手实现了:
Array.prototype.myReduce=function(callback, initialValue) {
let accumulator=initialValue ? initialValue : this[0];
for (let i=initialValue ? 0 : 1; i < this.length; i++) {
let _this=this;
accumulator=callback(accumulator, this[i], i, _this);
}
return accumulator;
};
// 使用
let arr=[1, 2, 3, 4];
let sum=arr.myReduce((acc, val)=> {
acc +=val;
return acc;
}, 5);
console.log(sum); // 15
先来看一个call实例,看看call到底做了什么:
let foo={
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
从代码的执行结果,我们可以看到,call首先改变了this的指向,使函数的this指向了foo,然后使bar函数执行了。总结一下:
思考一下:我们如何实现上面的效果呢?代码改造如下:
Function.prototype.myCall=function(context) {
context=context || window;
//将函数挂载到对象的fn属性上
context.fn=this;
//处理传入的参数
const args=[...arguments].slice(1);
//通过对象的属性调用该方法
const result=context.fn(...args);
//删除该属性
delete context.fn;
return result
};
我们看一下上面的代码:
以此类推,我们顺便实现一下apply,唯一不同的是参数的处理,代码如下:
Function.prototype.myApply=function(context) {
context=context || window
context.fn=this
let result
// myApply的参数形式为(obj,[arg1,arg2,arg3]);
// 所以myApply的第二个参数为[arg1,arg2,arg3]
// 这里我们用扩展运算符来处理一下参数的传入方式
if (arguments[1]) {
result=context.fn(…arguments[1])
} else {
result=context.fn()
}
delete context.fn;
return result
};
以上便是call和apply的模拟实现,唯一不同的是对参数的处理方式。
function Person(){
this.name="zs";
this.age=18;
this.gender="男"
}
let obj={
hobby:"看书"
}
// 将构造函数的this绑定为obj
let changePerson=Person.bind(obj);
// 直接调用构造函数,函数会操作obj对象,给其添加三个属性;
changePerson();
// 1、输出obj
console.log(obj);
// 用改变了this指向的构造函数,new一个实例出来
let p=new changePerson();
// 2、输出obj
console.log(p);
仔细观察上面的代码,再看输出结果。
我们对Person类使用了bind将其this指向obj,得到了changeperson函数,此处如果我们直接调用changeperson会改变obj,若用new调用changeperson会得到实例 p,并且其__proto__指向Person,我们发现bind失效了。
我们得到结论:用bind改变了this指向的函数,如果用new操作符来调用,bind将会失效。
这个对象就是这个构造函数的实例,那么只要在函数内部执行 this instanceof 构造函数 来判断其结果是否为true,就能判断函数是否是通过new操作符来调用了,若结果为true则是用new操作符调用的,代码修正如下:
// bind实现
Function.prototype.mybind=function(){
// 1、保存函数
let _this=this;
// 2、保存目标对象
let context=arguments[0]||window;
// 3、保存目标对象之外的参数,将其转化为数组;
let rest=Array.prototype.slice.call(arguments,1);
// 4、返回一个待执行的函数
return function F(){
// 5、将二次传递的参数转化为数组;
let rest2=Array.prototype.slice.call(arguments)
if(this instanceof F){
// 6、若是用new操作符调用,则直接用new 调用原函数,并用扩展运算符传递参数
return new _this(...rest2)
}else{
//7、用apply调用第一步保存的函数,并绑定this,传递合并的参数数组,即context._this(rest.concat(rest2))
_this.apply(context,rest.concat(rest2));
}
}
};
Currying的概念其实并不复杂,用通俗易懂的话说:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
function progressCurrying(fn, args) {
let _this=this
let len=fn.length;
let args=args || [];
return function() {
let _args=Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 参数收集完毕,则执行fn
return fn.apply(this, _args);
}
}
防抖函数 onscroll 结束时触发一次,延迟执行
function debounce(func, wait) {
let timeout;
return function() {
let context=this; // 指向全局
let args=arguments;
if (timeout) {
clearTimeout(timeout);
}
timeout=setTimeout(()=> {
func.apply(context, args); // context.func(args)
}, wait);
};
}
// 使用
window.onscroll=debounce(function() {
console.log('debounce');
}, 1000);
节流函数 onscroll 时,每隔一段时间触发一次,像水滴一样
function throttle(fn, delay) {
let prevTime=Date.now();
return function() {
let curTime=Date.now();
if (curTime - prevTime > delay) {
fn.apply(this, arguments);
prevTime=curTime;
}
};
}
// 使用
var throtteScroll=throttle(function() {
console.log('throtte');
}, 1000);
window.onscroll=throtteScroll;
乞丐版
JSON.parse(JSON.stringfy));
非常简单,但缺陷也很明显,比如拷贝其他引用类型、拷贝函数、循环引用等情况。
基础版
function clone(target){
if(typeof target==='object'){
let cloneTarget={};
for(const key in target){
cloneTarget[key]=clone(target[key])
}
return cloneTarget;
} else {
return target
}
}
写到这里已经可以帮助你应付一些面试官考察你的递归解决问题的能力。但是显然,这个深拷贝函数还是有一些问题。
一个比较完整的深拷贝函数,需要同时考虑对象和数组,考虑循环引用:
function clone(target, map=new WeakMap()) {
if(typeof target==='object'){
let cloneTarget=Array.isArray(target) ? [] : {};
if(map.get(target)) {
return target;
}
map.set(target, cloneTarget);
for(const key in target) {
cloneTarget[key]=clone(target[key], map)
}
return cloneTarget;
} else {
return target;
}
}
原理: L 的 proto 是不是等于 R.prototype,不等于再找 L.__proto__.__proto__ 直到 proto 为 null
// L 表示左表达式,R 表示右表达式
function instance_of(L, R) {
var O=R.prototype;
L=L.__proto__;
while (true) {
if (L===null){
return false;
}
// 这里重点:当 O 严格等于 L 时,返回 true
if (O===L) {
return true;
}
L=L.__proto__;
}
}
function myExtend(C, P) {
var F=function(){};
F.prototype=P.prototype;
C.prototype=new F();
C.prototype.constructor=C;
C.super=P.prototype;
}
就是利用 generator(生成器)分割代码片段。然后我们使用一个函数让其自迭代,每一个yield 用 promise 包裹起来。执行下一步的时机由 promise 来控制
function _asyncToGenerator(fn) {
return function() {
var self=this,
args=arguments;
// 将返回值promise化
return new Promise(function(resolve, reject) {
// 获取迭代器实例
var gen=fn.apply(self, args);
// 执行下一步
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
}
// 抛出异常
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
}
// 第一次触发
_next(undefined);
});
};
}
最近字节跳动的前端面试中也被面试官问到,要求手写实现。
Array.prototype.myFlat=function(num=1) {
if (Array.isArray(this)) {
let arr=[];
if (!Number(num) || Number(num) < 0) {
return this;
}
this.forEach(item=> {
if(Array.isArray(item)){
let count=num
arr=arr.concat(item.myFlat(--count))
} else {
arr.push(item)
}
});
return arr;
} else {
throw tihs + ".flat is not a function";
}
};
这个问题一般还会让你讲一讲事件冒泡和事件捕获机制
<ul id="color-list">
<li>red</li>
<li>yellow</li>
<li>blue</li>
<li>green</li>
<li>black</li>
<li>white</li>
</ul>
<script>
(function () {
var color_list=document.getElementById('color-list');
color_list.addEventListener('click', showColor, true);
function showColor(e) {
var x=e.target;
if (x.nodeName.toLowerCase()==='li') {
alert(x.innerHTML);
}
}
})();
</script>
Vue 2.x的Object.defineProperty版本
// 数据
const data={
text: 'default'
};
const input=document.getElementById('input');
const span=document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
// 数据变化 —> 修改视图
set(newVal) {
input.value=newVal;
span.innerHTML=newVal;
}
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
data.text=e.target.value;
});
Vue 3.x的proxy 版本
// 数据
const data={
text: 'default'
};
const input=document.getElementById('input');
const span=document.getElementById('span');
// 数据劫持
const handler={
set(target, key, value) {
target[key]=value;
// 数据变化 —> 修改视图
input.value=value;
span.innerHTML=value;
return value;
}
};
const proxy=new Proxy(data, handler);
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
proxy.text=e.target.value;
});
思考:Vue双向绑定的实现,使用 ES6 的 Proxy 相比 Object.defineProperty 有什么优势?
先看看reduce和map的使用方法
let new_array=arr.map(function callback(currentValue[, index[,array) {}[, thisArg])
let result=arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
最常见的方式我们可以用一个for循环来实现:
Array.prototype.myMap=function(callback, thisArg) {
let arr=[];
for (let i=0; i < this.length; i++) {
arr.push(callback.call(thisArg, this[i], i, this));
}
return arr;
};
同样的我们也可以用数组的reduce方法实现
Array.prototype.myMap2=function(callback, thisArg) {
let result=this.reduce((accumulator, currentValue, index, array)=> {
accumulator.push(callback.call(thisArg, currentValue, index, array));
return accumulator;
}, []);
return result;
};
看完如果觉得对你有帮助,劳烦点个赞哈,你的鼓励就是我更新最大的动力!
学习使我快乐!
种动态创建元素的区别
区别
1.document.write是直接将内容写入页面的内容流,但是文档流执行完毕,则它会导致页面全部重绘
2.innerHTML是将内容写入某个DOM节点,不会导致页面全部重绘
3.innerHTML创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结构稍微复杂(innerHTML采取数组形式拼接会快一点比createElement()快)
4.createElement()创建多个元素效率稍低一点点,但是结构更清晰
总结:不同浏览器下,innerHTML效率要比creatElement高
动态生成表格案例分析
2.所有的数据都是放到tbody里面的行里面。
3.因为行很多,我们需要循环创建多个行(对应多少人)
源码如下
效果图如下
见HTML ! 用纯Python就能写一个漂亮的网页
我们在写一个网站或者一个网页界面的时候,需要学习很多东西,对小白来说很困难!比如我要做一个简单的网页交互:
天啊,听听头都大呢!其实我就给老板做一个简单的交互的页面,而且我只会Python ,有没有很简单的办法可以做到呢。
今天小编在Github上逛的时候,找到一个非常酷的神器,名字叫remi ,目前收获了2300个赞。
再见HTML ! 用纯Python就能写一个漂亮的网页
star并不是很多,但是这个库可是选入2018年的十大明星库,号称是一个独立的GUI库,而且最牛逼的就是这个库竟然小于 100K,是否很神奇呢,我们一起来体验看一下特性:
安装
如何安装呢,因为是Python 库,直接用pip 即可
如果是没有网络的,或者服务器跟外网不通的,可以离线安装。下载这个包,然后用install 安装
快速体验
我们来快速看一下,这个简单的Hello world网页。
再见HTML ! 用纯Python就能写一个漂亮的网页
这里面包含了一个 2个元素:
点击这个按钮还会改变Hello world的文本内容,看点一下就变成了Button pressed.
再见HTML ! 用纯Python就能写一个漂亮的网页
上面这个简单的效果,其实只用了20来行Python代码,都是原生的Python代码,没有用一行HTML .
我们来看一下源码:
再见HTML ! 用纯Python就能写一个漂亮的网页
整个的代码的结构层次还很清晰的:
再见HTML ! 用纯Python就能写一个漂亮的网页
更多复杂的网页元素
看完上面的设计是不是就觉得跟Python里面大名鼎鼎的tk库很相似啊,上面的只是开胃菜,这个remi还能提供更复杂的gui界面元素。
再见HTML ! 用纯Python就能写一个漂亮的网页
上面的是一个完整的demo页面,基本上常见的控件元素的都包含了,比如有菜单栏,文本,按钮,单选框,进度栏,下拉框,表格,弹出框,按钮选取文件路径,文件树形结构,日期等几十种控件。
需要更多2020最新Python资料 私信小编“学习”即可获取。
*请认真填写需求信息,我们会在24小时内与您取得联系。