对象Object
如何证明一个属性是可枚举的?
for…in可遍历的就是可枚举的(不包含symbol,包含继承的属性)obj.hasOwnProperty是用来查询是否是自身的属性(包含自身不可枚举,不包含继承)obj.propertyIsEnumerable是用来查询自身的属性是否可枚举(包含symbol)
遍历对象
for...in- 遍历自身、继承的可枚举属性(不含Symbol)Object.keys()- 仅遍历自身可枚举属性(不含Symbol)Object.getOwnPropertyNames()- 遍历自身的可枚举属性和不可枚举属性
Generator 生成器
一个函数本来只能有一个return 返回值。但是yield可以让函数执行中有多次的返回。可以暂停函数执行。
示例1 - 自增ID:
要生成一个自增的ID,可以编写一个next_id()函数:
var current_id = 0;function next_id() {current_id ++;return current_id;}
由于函数无法保存状态,故需要一个全局变量current_id来保存数字。
不用闭包,试用generator改写:
function* next_id() {var i = 1;while(true) yield i++;}
示例2 - 斐波那契数列:
function* fib(max) {var a = 0,b = 1,n = 0while (n < max) {yield a;[a, b] = [b, a + b]n++}return}for (var x of fib(10)) {console.log(x) // 依次输出0, 1, 1, 2, 3, ...}
JS函数柯里化(Currying)的概念
概念:
**
柯里化(Currying),是把接收多个参数的函数变换成接收一个单一参数(最初函数的第一个参数)的函数,并返回接受剩余的参数而且返回结果的新函数的技术。
常见作用:
- 参数复用 - 如:
curryingAdd(1)(2) - 提前返回
- 延迟计算/运行
手写 - Promise
Promise/A+规范:
- 有三种状态
pending | resolved | rejected。 - 处于
pending时,可以转移到resolved或者rejected状态。 - 处于
resolve或reject时,就不可变。 - 必须有一个
then异步执行方法,then接受两个参数且必须返回一个promise。流程图:
代码:
```javascript function myPromise(constructor){ let self=this; self.status=”pending” //定义状态改变前的初始状态 self.value=undefined;//定义状态为resolved的时候的状态 self.reason=undefined;//定义状态为rejected的时候的状态 function resolve(value){
} function reject(reason){//两个==="pending",保证了状态的改变是不可逆的if(self.status==="pending"){self.value=value;self.status="resolved";}
} //捕获构造异常 try{//两个==="pending",保证了状态的改变是不可逆的if(self.status==="pending"){self.reason=reason;self.status="rejected";}
}catch(e){constructor(resolve,reject);
} }reject(e);
// 同时,需要在myPromise的原型上定义链式调用的then方法:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case “resolved”:
onFullfilled(self.value);
break;
case “rejected”:
onRejected(self.reason);
break;
default:
}
}
<a name="1TIHX"></a># 手写 - 防抖(debounce)<a name="MlvUG"></a>## 原理:函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。(例:监听input输入框值的变化,用非立即执行)- 立即执行:多次触发事件,第一次会立即执行函数,之后在设定wait时间内触犯的事件无效,不会执行。- 非立即执行:多次触发事件,只会在最后一次触发事件后等待设定的wait时间结束时执行一次。<br /><a name="s0R3A"></a>## 代码:```javascriptfunction debounce(fn, wait = 50, immediate) {let timerreturn function () {// timer不为null,清除定时器if (timer) clearTimeout(timer)if (immediate) {// timer为null时,可以执行if (!timer) fn.apply(this, arguments)// 在 wait 时间后将timer 变为null,代表可以执行timer = setTimeout(() => {timer = null}, wait)} else {timer = setTimeout(() => {fn.apply(this, arguments)}, wait)}}}
手写 - 节流(throttle)
原理:
代码:
function throttle(fn, wait) {let prev = new Date()return function () {const now = new Date()if (now - prev > wait) {fn.apply(this, arguments)prev = now}}}
手写 - call、apply、bind
call、apply、bind原理
代码示例:
var Tom = {name: 'tom',age: 16,fn: function (from, to) {console.log(`name: ${this.name}, age: ${this.age}; from: ${from}, to: ${to}`)},}var Eric = {name: 'eric',age: 18,}Tom.fn('成都', '上海') // name: tom, age: 16; from: 成都, to: 上海Tom.fn.call(Eric, '成都', '上海') // name: eric, age: 18; from: 成都, to: 上海Tom.fn.apply(Eric, ['成都', '上海']) // name: eric, age: 18; from: 成都, to: 上海Tom.fn.bind(Eric, '成都', '上海')() // name: eric, age: 18; from: 成都, to: 上海
相同:
- 三者都可以让一个对象去使用其他对象中的方法,并将
this指向当前对象。 -
不同:
call:参数如同普通函数的传参,从第二个参数开始。直接返回函数调用结果。apply:与call相似,但参数必须以数组的形式,传入第二个参数中。 直接返回函数调用结果。bind:参数传参形式与call相同。返回值是一个函数,需要再次调用。bind实现还需要考虑实例化后对原型链的影响。
实现 call()
/*** 1. 创建一个独一无二的key,避免key值冲突* 2. 将函数挂载在obj上* 3. 调用,获取返回值。传参如同普通函数调用,一个个传入。* 4. 删除key* 5. return 返回值*/Function.prototype.myCall = function (obj, ...args) {const key = Symbol()obj[key] = thisconst res = obj[key](...args)delete obj[key]return res}Tom.fn.myCall(Eric, '成都', '上海') // name: eric, age: 18; from: 成都, to: 上海
实现 apply()
Function.prototype.myApply = function (obj) {// 逻辑上与 call 一致,仅传参有区别。const key = Symbol()obj[key] = this// 判断是否有第二个参数,以数组形式接收,正常参数列表传参。let resif (arguments[1]) {res = obj[key](...arguments[1])} else {res = obj[key]()}delete obj[key]return res}Tom.fn.myApply(Eric, ['成都', '上海']) // name: eric, age: 18; from: 成都, to: 上海
实现 bind()
Function.prototype.myBind = function (obj, ...bindArgs) {// 接收参数类似call。返回一个函数。还要考虑实例化后对原型链的影响// 不是function要抛出错误if (typeof this != 'function') {throw Error('not a function')}let fn = thisfunction resFn(...resArgs) {// 若当前函数this指向的是构造函数中的this,则判定为new操作。const resThis = this instanceof resFn ? this : obj// 接收参数,bind时绑定或此函数传参return fn.call(resThis, ...bindArgs, ...resArgs)}// 维持原型关系,避免直接prototype互相赋值,用空函数来中继function tmp() {}tmp.prototype = fn.prototyperesFn.prototype = new tmp()return resFn}Tom.fn.myBind(Eric, '成都', '上海')() // name: eric, age: 18; from: 成都, to: 上海
手写 - new
/*** new 做的事情* 1. 创建一个全新的对象。* 2. 执行 prototype 链接。* 3. 使 this 指向新创建的对象。* 4. 如果函数没有返回对象类型(含Functoin, Array, Date, RegExg, Error),* 那么 new 将返回该对象引用。*/function New(fn) {var obj = {__proto__: fn.prototype,}var res = fn.apply(obj, Array.prototype.slice.call(arguments, 1))if (res !== null && (typeof res === 'object' || typeof ret === 'function')) {return res}return obj}// 使用示例function Person(name) {this.name = name}const Jim = new Person('Jim') // Person {name: 'Jim'}const Bob = New(Person, 'Bob') // Person {name: 'Bob'}
手写 - instanceOf
instanceOf 用来判断一个对象的 prototype 是否在另外另外一个对象的原型链上。
function myInstanceOf(left, right) {const rightProto = right.prototypelet leftProto = left.__proto__while (true) {// 所有 __proto__ 都检测完了,不在原型链上,返回falseif (leftProto === null) return false// 在原型链上,返回trueif (leftProto === rightProto) return true// 往上层继续查找leftProto = leftProto.__proto__}}myInstanceOf({}, Object) // true
手写 - 深拷贝
丐版:
function deepClone(obj) {return JSON.parse(JSON.stringify(obj))}
面试够用版:
function deepClone(obj) {// 判断是否为复杂类型,兼容数组,复杂类型递归调用if (typeof obj === 'object') {var res = obj.constructor === Array ? [] : {}for (const key in obj) {res[key] = deepClone(obj[key])}return res}// 简单类型,直接赋值return obj}
实现一个继承
寄生组合式继承:
// 定义父类 Animalfunction Animal(name) {this.name = namethis.sleep = function () {console.log(this.name + '正在睡觉!')}}Animal.prototype.eat = function (food) {console.log(this.name + '正在吃:' + food)}// 定义子类 Catfunction Cat(name, color) {// 组合,继承父类构造函数的属性,可传参Animal.call(this, name)this.color = colorthis.show = function () {console.log(`${this.name}是${this.color}色的`)}}// 寄生function extendProto(child, parent) {// 创建一个空的函数,用以继承父类原型,同时又不会继承实例属性。function F() {}F.prototype = parent.prototypechild.prototype = new F()// 修复实例child.prototype.constructor = child}extendProto(Cat, Animal)// 实例化var cat = new Cat('Tom', '白')console.log(cat.name) // Tomconsole.log(cat.color) // 白cat.sleep() // Tom正在睡觉!cat.show() // Tom是白色的cat.eat('鱼') // Tom正在吃:鱼console.log(cat instanceof Animal) // trueconsole.log(cat instanceof Cat) //true
ES6继承:
// 创建父类 Animalclass Animal {constructor(name) {this.name = name}sleep() {console.log(`${this.name}正在睡觉!`)}eat(food) {console.log(`${this.name}正在吃${food}`)}}// 创建子类 Catclass Cat extends Animal {constructor(name, color) {super(name)this.color = color}show() {console.log(`${this.name}是${this.color}色的`)}}// 实例化var cat = new Cat('Tom', '白')console.log(cat.name) // Tomconsole.log(cat.color) // 白cat.sleep() // Tom正在睡觉!cat.show() // Tom是白色的cat.eat('鱼') // Tom正在吃:鱼console.log(cat instanceof Animal) // trueconsole.log(cat instanceof Cat) //true
