前端秋招部分简单手撕题
- 1.将1-52中的所有数字随机分为四组
- 2.对象扁平化
- 3.数组扁平化
- 4.括号匹配
- 5.事件委托
- 6.合法的URL
- 7.全排列
- 8._objectCreate
- 9._call函数
- 10.Function.bind
- 11.实现new操作符
- 12. Object.freeze
- 13.浅拷贝
- 14.简易深拷贝
- 15.深拷贝
- 16. 寄生组合式继承
- 17. 发布订阅模式
- 18. 观察者模式
- 19. 二叉树最底层最左边的值
- 20. 快速排序
- 21. 手写防抖
- 22. 手写节流
- 23. 原型链继承
- 24. 借用构造函数继承
- 25. 组合继承:原型链 + 构造函数 (这是js中最常见的继承方式)
- 26. 原型式继承 (注意:是原型式而非原型链,这种方法使用较少)
- 27. 寄生式继承
- 28寄生组合式继承 (寄生+组合(原型链+借用构造函数)
- 29. es6继承
1.将1-52中的所有数字随机分为四组
function cutArray(arry) {
let a = [],
b = [],
c = [],
d = [];
while (a.length < 13) {
let i = Math.floor(Math.random() * arry.length + 1); //产生随机数组下标。为啥不写52,而是写arry.length是因为arry是变化的,看下面代码。
a.push(arry[i-1]);
arry.splice(i-1, 1); //改变arry,将已分配的数据删除
}
while (b.length < 13) {
let i = Math.floor(Math.random() * arry.length + 1);
b.push(arry[i-1]);
arry.splice(i-1, 1);
}
while (c.length < 13) {
let i = Math.floor(Math.random() * arry.length + 1);
c.push(arry[i-1]);
arry.splice(i-1, 1);
}
d = arry;
}
2.对象扁平化
function objectFlat(data) {
let newObj = {};
let process = (key, value) => {
if (Object(value) !== value) {
//普通类型、null、undefind
newObj[key] = value;
} else {
//引用类型
if (Array.isArray(value)) {
value.forEach((childVal, childIndex) => {
process(`${key}[${childIndex}]`, childVal);
});
//空数组赋值空数组
if (value.length === 0) newObj[key] = [];
} else {
const keyArr = Object.keys(value);
keyArr.forEach((childKey) => {
process(`${key}.${childKey}`, value[childKey]);
});
//为空赋值空对象
if (keyArr.length === 0) newObj[key] = {};
}
}
};
Object.keys(data).forEach((key) => process(key, data[key]));
return newObj;
}
3.数组扁平化
const _flatten = arr => {
var newarr = []
arr.forEach(i=>{
if(Array.isArray(i)){
newarr = newarr.concat(_flatten(i))
}
else newarr.push(i)
})
return newarr
}
4.括号匹配
function isValid(str){
let strArr = str.split(''),
left = [];// 空栈
for(let i=0;i<strArr.length;i++){
if(strArr[i] == '(' || strArr[i] == '[' || strArr[i] == '{'){
left.push(strArr[i]) //左括号入栈
}else{
if(strArr[i] == ')' && left.pop() != '('){
return false //结束循环
}
if(strArr[i] == ']' && left.pop() != '['){
return false
}
if(strArr[i] == '}' && left.pop() != '{'){
return false
}
}
}
return left.length == 0
}
5.事件委托
- 给"ul"标签添加点击事件
- 当点击某"li"标签时,该标签内容拼接".“符号。如:某"li"标签被点击时,该标签内容为”…"
注意: - 必须使用DOM0级标准事件(onclick)
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<ul>
<li>.</li>
<li>.</li>
<li>.</li>
</ul>
<script type="text/javascript">
// 补全代码
document.querySelector('ul').onclick = function(e){
e.target.innerHTML+='.'
}
</script>
</body>
</html>
6.合法的URL
要求以Boolean的形式返回字符串参数是否为合法的URL格式。
注意:1. 协议仅为HTTP(S)
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const _isUrl = url => {
// 补全代码
let reg = /^((http|https):\/\/)?(([a-zA-Z0-9]+-[a-zA-Z0-9]+|[a-zA-Z0-9]+)\.)+([a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?/
return reg.test(url)
}
</script>
</body>
</html>
7.全排列
const _permute = string => {
// 补全代码
var res = []
if (string.length == 1) {
res.push(string)
} else if (string.length > 1) {
for (let i = 0; i < string.length; i++) {
var current = string[i]
var rest = string.slice(0, i) + string.slice(i + 1, string.length)
var preRes = _permute(rest)
for (let m = 0; m < preRes.length; m++) {
var tmp = current + preRes[m]
res.push(tmp)
}
}
}
return res
}
8._objectCreate
要求实现Object.create函数的功能且该新函数命名为"_objectCreate"
const _objectCreate = proto => {
// 补全代码
if(typeof proto !=='Object' || proto ==null) return
const fn = function(){}
fn.prototype = proto
return new fn()
}
9._call函数
要求实现Function.call函数的功能且该新函数命名为"_call"。
Function.prototype._call = function(target=window){
target['fn']=this
const result = target['fn']([...arguments].shift())
delete target['fn']
return result
}
10.Function.bind
要求实现Function.bind函数的功能且该新函数命名为"_bind"。
Function.prototype._bind = function(target,...arguments1){
const _this = this
return function(...arguments2) {
return _this.apply(target,arguments1.concat(arguments2))
}
}
11.实现new操作符
const _new = function() {
// 补全代码
var obj = {}
var fn = [...arguments].shift()
obj.__proto__ = fn.prototype
const obj2 = fn.apply(obj,arguments)
return obj2 instanceof Object? obj2:obj
}
12. Object.freeze
const _objectFreeze = object => {
// 补全代码
const keys = Object.getOwnPropertyNames(object)
const symbols=Object.getOwnPropertySymbols(object)
;[...keys,...symbols].forEach(key=>{
Object.defineProperty(object,key,{
configurable:false,
writable:false
})
})
Object.seal(object)
}
13.浅拷贝
const _shallowClone = target => {
// 补全代码
return Object.assign({},target)
}
14.简易深拷贝
要求实现对象参数的深拷贝并返回拷贝之后的新对象。
注意:1. 参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中2. 无需考虑循环引用问题
const _sampleDeepClone = target => {
// 补全代码
var a= JSON.parse(JSON.stringify(target))
return a
}
15.深拷贝
要求实现对象参数的深拷贝并返回拷贝之后的新对象。
注意:1. 需要考虑函数、正则、日期、ES6新对象2. 需要考虑循环引用问题
const _completeDeepClone = (target, map = new Map()) => {
// 补全代码
// 直接返回基础数据类型及函数类型
if (target === null || typeof target !== 'object') return target
if (target.constructor === Date) return new Date(target)
if (target.constructor === RegExp) return new RegExp(target)
//循环引用
if (map.has(target)) return map.get(target)
//保证原型链与属性的特性一致
let clone = Object.create(Object.getPrototypeOf(target), Object.getOwnPropertyDescriptors(target))
//添加到map
map.set(target, clone)
//遍历属性
const keys = Object.getOwnPropertyNames(target)
const symbols = Object.getOwnPropertySymbols(target);
[...keys, ...symbols].forEach(key => {
clone[key] = _completeDeepClone(clone[key], map)
})
return clone
}
16. 寄生组合式继承
要求通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
- 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性
function Human(name) {
this.name = name
this.kingdom = 'animal'
this.color = ['yellow', 'white', 'brown', 'black']
}
Human.prototype.getName = function(){
return this.name
}
function Chinese(name,age) {
Human.call(this,name)
this.age = age
this.color = 'yellow'
}
Chinese.prototype = Object.create(Human.prototype)
Chinese.prototype.constructor = Chinese
Chinese.prototype.getAge = function(){
return this.age
}
17. 发布订阅模式
完成"EventEmitter"类实现发布订阅模式。
注意:1. 同一名称事件可能有多个不同的执行函数2. 通过"on"函数添加事件3. 通过"emit"函数触发事件
class EventEmitter {
constructor(){
this.events = {}
}
on(e,fn){
if(!this.events[e]){
this.events[e]=[fn]
}else{
this.events[e].push(fn)
}
}
emit(e){
if(this.events[e]){
this.events[e].forEach((callback)=>{
callback()
})
}
}
}
18. 观察者模式
完成"Observer"、"Observerd"类实现观察者模式。要求如下:
- 被观察者构造函数需要包含"name"属性和"state"属性且"state"初始值为"走路"2. 被观察者创建"setObserver"函数用于保存观察者们3. 被观察者创建"setState"函数用于设置该观察者"state"并且通知所有观察者4. 观察者创建"update"函数用于被观察者进行消息通知,该函数需要打印(console.log)数据,数据格式为:小明正在走路。其中"小明"为被观察者的"name"属性,"走路"为被观察者的"state"属性
注意:1. "Observer"为观察者,"Observerd"为被观察者
class Observerd {
constructor(name){
this.name = name
this.state = '走路'
this.observers = []
}
setObserver(observer){
this.observers.push(observer)
}
setState(state){
this.state = state
this.observers.forEach(observer=>{
observer.update(this)
})
}
}
class Observer {
constructor(){}
update(observerd){
console.log(observerd.name+'正在'+observerd.state)
}
}
19. 二叉树最底层最左边的值
var findBottomLeftValue = function (root) {
let res, maxLevel = 0;
function dfs(node, level) {
if (!node) return;
if (level > maxLevel) res = node, maxLevel = level;
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
dfs(root, 1);
return res && res.val;
};
20. 快速排序
function quictSort(arr){
if(arr.length<=1) return arr
var pointIndex = Math.floor(arr.length/2)
var point = arr.splice(pointIndex,1)[0]
var left = []
var right = []
for(var i=0;i<arr.length;i++){
if(arr[i]<point) left.push(arr[i])
else right.push(arr[i])
}
return quictSort(left).concat([point],quictSort(right))
}
21. 手写防抖
var debounce = function(fn,delay){
let timer=null
return function(){
clearTimeout(timer)
let args = arguments
timer=setTimeout(()=>{
fn.apply(this,args)
},delay)
}
}
22. 手写节流
function jieliu(fn,time){
var start =0
return function(){
var end = new Date()
if(end-start>time){
fn.apply(this,arguments)
start = end
}
}
}
23. 原型链继承
缺点:Parent 中的引用属性会被每个子类示例共享
function Parent(){
this.parentPrototype = 'parent prototype'
//验证这种继承方法的确定,如果父类示例中存在一个引用类型的属性,将会被所有子类共享
this.parentObj = {
info:'缺点:Parent 中的引用属性会被每个子类示例共享'
}
}
function Children(){}
//将Children的原型对象指定为Parent的示例,通过原型链,将Parent中的属性赋值给Children示例
Children.prototype = new Parent()
const a = new Children()
console.log(a.parentPrototype);// parent prototype
//缺点
const b = new Children()
//在a示例中改动继承的引用属性
a.parentObj.info = "我是a示例中 引用属性parentObj中的 info"
//b与a示例共享引用属性
console.log(b.parentObj.info); // 我是a示例中 引用属性parentObj中的 info
24. 借用构造函数继承
优点:
1避免了子类实例共享引用属性的情况
2可以在实例化时给Parent构造函数传递参数
缺点:
1如果Parent中存在一个函数,那么每次实例化Children的时候,都会创建一个同样函数,函数的复用性就难以体现
function Parent() {
this.parentPrototype = "parent prototype"
this.obj = {
info: "parent obj info"
}
this.fn = function () {
console.log("打印功能")
}
}
function Children() {
Parent.call(this);
}
const a = new Children();
console.log(a.parentPrototype); // parent ptototype
//缺点 此时Parent()会再次创建一个fn函数,这个是没有必要的
const b = new Children();
a.obj.info = "a obj info";
//优点 避免了子类实例共享引用属性
console.log(b.obj.info) // parent obj info;
25. 组合继承:原型链 + 构造函数 (这是js中最常见的继承方式)
优点:避免了子类共享引用属性同时避免了父类构造函数重复对function属性的创建
function Parent() {
this.parentPrototype = "我是Parent 中的属性"
}
//Parent中的方法,在原型上定义
Parent.prototype.pFn = function () {
console.log('我是Parent中的方法');
}
function Children() {
//Parent中的属性仍然在构造函数中继承
Parent.call(this);
}
//将Children的原型对象赋值为 Parent实例,这样Parent中的方法也能够被Children继承
Children.prototype = new Parent();
const c = new Children();
console.log(c.parentPrototype); //我是Parent 中的属性
c.pFn(); //我是Parent中的方法
26. 原型式继承 (注意:是原型式而非原型链,这种方法使用较少)
缺点:和原型链继承一样,后代实例会共享父类引用属性
function objFn(o) {
o.objFnPrototype = "我是 objFnPrototype"
function F() {}
F.prototype = o;
return new F();
}
let a = objFn({
name: "name1"
});
console.log(a.name); //name1
console.log(a.objFnPrototype); //我是 objFnPrototype
27. 寄生式继承
缺点:和原型链继承一样,parent中的引用属性,会被所有示例共享
function createObje(obj) {
let clone = Object.assign(obj); //接受到对象后,原封不动的创建一个新对象
clone.prototype1 = "我是新增的prototype1"; //在新对象上新增属性,这就是所谓的寄生
return clone; //返回新对象
}
const parent = {
parentPrototype: "parentPrototype"
}
//c实例,就继承了parent的所有属性
let c = createObje(parent);
console.log(c.parentPrototype); //parentPrototype
28寄生组合式继承 (寄生+组合(原型链+借用构造函数)
优点:和组合继承一样,只不过没有组合继承的调用两次父类构造函数的缺点
function inherProto(superType, subType) {
//拷贝一个超类的原型副本
let proto = {
...superType.prototype
};
//将原型的超类副本作为子类的原型对象,也就是第一种中的原型链继承方式,只不过继承的是超类原型的副本
subType.prototype = proto;
//这一步比较迷,官方的说法是,我们在拷贝超类的原型的时候,拷贝的proto对象,将会丢失默认自己的构造函数,也就是superType,
//所以我们这里将它的构造函数补全为subType。貌似不做这一步也没啥问题,但是缺了点东西可能会有其他的副作用,所以还是补上
proto.constructor = subType;
}
function Super() {
this.superProto = "super proto";
this.colors = ["red", "yelloy"];
}
function Sub() {
this.subProto = "sub proto";
this.name = "sub name";
//这里还是借用构造函数的套路
Super.call(this);
}
Super.prototype.getName = function () {
console.log(this.name);
}
//这里要在定义完Super的属性后执行,因为继承的是超类原型的副本,与Super.prototype是两个对象,在这之后再改变Super.prototype,就已经不会在影响到Sub所继承的副本超类原型对象了
inherProto(Super, Sub);
let a = new Sub();
console.log(a.getName);
29. es6继承
//class 相当于es5中构造函数
//class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
//class中定义的所有方法是不可枚举的
//class中只能定义方法,不能定义对象,变量等
//class和方法内默认都是严格模式
//es5中constructor为隐式属性
class People{
constructor(name='wang',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
//继承父类
class Woman extends People{
constructor(name = 'ren',age = '27'){
//继承父类属性
super(name, age);
}
eat(){
//继承父类方法
super.eat()
}
}
let wonmanObj=new Woman('xiaoxiami');
wonmanObj.eat();