高级函数
安全类型的检测
例如:
1 2 3 4 5
| let isArray = value => Object.prototype.toString.call(value) === '[object Array]' let isFunction = value => Object.prototype.toString.call(value) === '[object Function]' let isRegExp = value => Object.prototype.toString.call(value) === '[object RegExp]'
|
作用域安全的构造函数
例如以下的构造函数:
1 2 3
| function Person(name){ this.name = name }
|
如果我们不是new出来的,而是直接将构造函数当做一个普通的函数调用就会将name
属性绑定到window上面(this指向window):
以上的操作为window添加了新的属性,导致window的属性意外增加或者覆盖!解决方案是创建一个作用域安全的构造函数:
1 2 3 4 5 6
| function Person(name){ if(this instanceof Person) this.name = name else return new Person(name) }
|
以上的函数要么使用现有的实例,要么创建新的实例,this指向的始终是实例,避免了在全局对象上意外增加属性.这是最佳实践.
js中的类和继承
类定义的方法
构造函数定义类
1 2 3 4 5 6 7 8 9
| function Person(){ this.name = 'michaelqin'; } Person.prototype.sayName = function(){ alert(this.name); }
var person = new Person(); person.sayName();
|
对象创建方法定义类
1 2 3 4 5 6 7
| var Person = { name: 'michaelqin', sayName: function(){ alert(this.name); } };
var person = Object.create(Person); person.sayName();
|
ES5推荐使用第二种模式,但是原型法更普遍。
类继承的方法
原型链
1 2 3 4 5 6 7 8 9 10
| function Animal() { this.name = 'animal'; } Animal.prototype.sayName = function(){ alert(this.name); };
function Person() {} Person.prototype = Animal.prototype; Person.prototype.constructor = 'Person';
|
属性复制
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Animal() { this.name = 'animal'; } Animal.prototype.sayName = function() { alert(this.name); };
function Person() {}
for(prop in Animal.prototype) { Person.prototype[prop] = Animal.prototype[prop]; } Person.prototype.constructor = 'Person';
|
ES5中的继承是通过借用父类的构造方法来实现方法/属性的继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| function supFather(name) { this.name = name; this.colors = ['red', 'blue', 'green']; }
supFather.prototype.sayName = function (age) { console.log(this.name, 'age'); };
function sub(name, age) { supFather.call(this, name); this.age = age; }
function inheritPrototype(sonFn, fatherFn) { sonFn.prototype = Object.create(fatherFn.prototype); sonFn.prototype.constructor = sonFn; }
inheritPrototype(sub, supFather); sub.prototype.sayAge = function () { console.log(this.age, 'foo'); };
const instance1 = new sub("OBKoro1", 24); const instance2 = new sub("小明", 18); instance1.colors.push('black')
console.log(instance1);
console.log(instance2);
|
js中多重继承的实现就是使用属性复制来实现的,因为当父类的prototype
属性被复制后,子类自然拥有类似的行为和属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| const mixinClass = (base, ...mixins) => { const mixinProps = (target, source) => { Object.getOwnPropertyNames(source).forEach(prop => { if (/^constructor$/.test(prop)) { return; } Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop)); }) };
let Ctor; if (base && typeof base === 'function') { Ctor = class extends base { constructor(...props) { super(...props); } }; mixins.forEach(source => { mixinProps(Ctor.prototype, source.prototype); }); } else { Ctor = class {}; } return Ctor; };
class A { methodA() {} } class B { methodB() {} } class C extends mixinClass(A, B) { methodA() { console.log('methodA in C'); } methodC() {} }
let c = new C(); c instanceof C c instanceof A c instanceof B
|
设计模式
工厂
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Person() { this.name = 'Person1'; } function Animal() { this.name = 'Animal1'; }
function Factory() {} Factory.prototype.getInstance = function(className) { return eval('new ' + className + '()'); }
var factory = new Factory(); var obj1 = factory.getInstance('Person'); var obj2 = factory.getInstance('Animal'); console.log(obj1.name); console.log(obj2.name);
|
代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Person() { } Person.prototype.sayName = function() { console.log('michaelqin'); } Person.prototype.sayAge = function() { console.log(30); }
function PersonProxy() { this.person = new Person(); var that = this; this.callMethod = function(functionName) { console.log('before proxy:', functionName); that.person[functionName](); console.log('after proxy:', functionName); } }
var pp = new PersonProxy(); pp.callMethod('sayName'); pp.callMethod('sayAge');
|
观察者,又称事件模式,例如按钮的onclick
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function Publisher() { this.listeners = []; } Publisher.prototype = { 'addListener': function(listener) { this.listeners.push(listener); },
'removeListener': function(listener) { delete this.listeners[listener]; },
'notify': function(obj) { for(var i = 0; i < this.listeners.length; i++) { var listener = this.listeners[i]; if (typeof listener !== 'undefined') { listener.process(obj); } } } };
function Subscriber() {
} Subscriber.prototype = { 'process': function(obj) { console.log(obj); } };
var publisher = new Publisher(); publisher.addListener(new Subscriber()); publisher.addListener(new Subscriber()); publisher.notify({name: 'michaelqin', ageo: 30}); publisher.notify('2 subscribers will both perform process');
|
原型式污染
1 2 3 4 5 6 7
| let foo = {bar:1} foo.bar foo.__proto__.bar = 233 foo.bar let zoo = {} zoo.bar Object.prototype
|
而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为233,后来,我们又用Object类创建了一个zoo对象let zoo = {},zoo对象自然也有一个bar属性了。
那么,在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
js面试题
1 2 3 4 5
| var b = 10; (function b(){ b = 20; console.log(b); })();
|
输出:
1 2 3 4
| ƒ b(){ b = 20; console.log(b); }
|
主要考察作用域和var的变量提升,如果把匿名函数中的b = 20
改成var b = 20
,则会输出20,上面的b其实是个函数。
1 2 3 4 5 6 7 8 9 10
| function func() { console.log(this.a); } var a = 2; var o = { a: 3, func: func }; var p = { a: 4 }; o.func(); (p.func = o.func)();
|
如何实现一个深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const obj = { a: 1, b: [1, 2, 3, 4, 5], c: { d: 12 } }; obj.obj = obj;
function cloneDeep(obj, map = new WeakMap()) { if (typeof obj === 'object') { if (map.has(obj)) { return map.get(obj); } const clone = Array.isArray(obj) ? [] : {}; map.set(obj, clone); for (const key of Object.keys(obj)) { clone[key] = cloneDeep(obj[key], map); } return clone; } return obj; }
const copy = cloneDeep(obj); copy.obj.b.push(-1); console.log(obj, copy);
|
注意:我们常用Object.assign({},obj)
来拷贝对象,但是这其实是浅拷贝,可以参考lodash.cloneDeep
。
ES6 let关键字的坑
不能被重复声明
1 2 3 4
| function fn (a){ let a = 2; console.log(a); }
|
暂时性死区
1 2 3 4 5
| let a = 'outside'; if(true) { console.log(a); let a = "inside"; }
|
js中的字符串
字符串是不可变对象,意味着我们不能单独改变某一特定索引的字符,例如:
1 2
| str = '123'; str[0]='a';
|
解构的妙用
常用在配置的默认值初始化,例如
1 2 3
| options = {host:null,port:null,...options}
options = Object.assign({host:null,port:null},options)
|