理念: 尽信书则不如无书
Java/C++
中是有函数重载的(即相同函数名, 但传的参数不同是当成不同函数的), 而 JavaScript
没有函数重载, 旧其本质是因为 JavaScrit
中函数是一个对象。函数名类似一个指针。
区分以下两段函数:
let num = 0for (let i = 0; i < 10; i++) {for (let j = 0; j < 10; j++) {if (i === 5 && j === 5) {break}num++}}console.log(num) // 95
使用 label 语句:
let num = 0outPoint:for (let i = 0; i < 10; i++) {for (let j = 0; j < 10; j++) {if (i === 5 && j === 5) {break outPoint}num++}}console.log(num) // 55
function
开头的是函数声明, 不是以 function
开头的则是函数表达式// 函数声明function test() {}// 函数表达式const test = function() {}// 函数表达式(function(){})()
函数声明提升
var a = function() {test()function test() {console.log('函数声明提升')}}a()
大体针对 Object.defineProperty()
、Object.defineProperties
这两个 api 来讲的。使用这两个 api 创建的对象里的数据类型和访问器类型默认为 false(Configuble、Enummerable、Writable) 以及 undefined(Value、Set、Get)。
注意: 在对象上直接定义的属性, Configurable、Enumerable、Writable 默认为 true
这部分知识点和继承相通, 可联系起来;
function createPeople(name, age) {const obj = new Object()obj.name = nameobj.age = agereturn obj}createPeople('Jack', 10)
缺点: 不知道创造的对象属于什么类
function People(name, age) {this.name = namethis.age = agethis.sayHi = function() { console.log('hi') }}const people1 = new People('Jack', 10)const people2 = new People('Lucy', 8)people1.sayHi === people2.sayHi // false
优点: 能将自定义参数传入构造函数 缺点: 没有解决公共方法的复用性(是缺点也是优点, 后面有用到这个特性)
function People(name, age) {this.name = namethis.age = age}People.prototype.sayHi = function() { console.log('hi') }People.prototype.habbit = ['reading']const people1 = new People('Jack', 10)const people2 = new People('Lucy', 8)
原型模式实际上必须结合构造函数一起使用, 但在这里为了说明原型模式的缺点, 单独列了出来。
// 结果people1.sayHi === people2.sayHi // truepeople1.habbit.push('drawing')people2.habbit // ['reading', drawing]
优点: 解决公共方法的复用性(sayHi); 缺点: 也正是复用性, 所以在一个实例上修改 prototype 上的属性会对其它实例也产生相同影响(habbit);
这个模式也是目前被大家最为认可的一种方式, 对上述例子稍作修改:
function People(name, age) {this.name = namethis.age = agethis.habbit = ['reading']}People.prototype.sayHi = function() { console.log('hi') }const people1 = new People('Jack', 10)const people2 = new People('Lucy', 8)// 结果people1.sayHi === people2.sayHi // truepeople1.habbit.push('drawing')people2.habbit // ["reading"]
现在能直观地看到, people1 和 people2 公用同一个 sayHi 方法, 但是其它的属性 name、age、habbit 都是各自独立拥有的。
结论: 所谓的构造函数模式 + 原型链模式即公有方法使用原型链模式, 私有方法使用构造函数模式;从而发挥各自的优点。
提到块级作用域可以联系到 'const/let 出现的原因' 或者 'var 的缺点'
// 案例 1var a = 1var aa // 1------------// 案例 2var a = 1var a = 2a // 2
可以看到使用 var 并不会告知之前是否已经声明过该变量, 案例 1 直接无视了后续的声明, 案例 2 后续的声明覆盖了前面的声明, 这样子使用起来便有些混乱了。这也是 const/let 出现的原因, const 专注案例 1 的情形, let 则专注案例 2 的情形。
// 案例 1const a = 1const a// Uncaught SyntaxError: Missing initializer in const declaration------------// 案例 2let a = 1let a = 2a // 2
关键字: 匿名函数
(function() {// 块级作用域})()
因为没有引用指向匿名函数, 所以执行完就可以垃圾回收, 不造成内存浪费。
function Safe(value) {if (this instanceof Safe) {this.name = value} else {return new Safe(value)}}
这种写法不管使用 new Safe()
还是 Safe()
能保证它们返回结果一致(作用域一致)。
Object.preventExtensions(obj)
: obj 不能添加属性Object.seal(obj)
: obj 不能添加/删除属性Object.freeze(obj)
: obj 不能添加/删除/修改属性防抖: 多次触发事件只执行一次(适用于断续的事件, 比如 click
、input
)
function debounce(fn, time) {let timeoutreturn () => {if (timeout) {clearTimeout(timeout)}timeout = setTimeout({fn}, time)}}
建议: 面试的时候先写出如上形式, 如果有时间再考虑实现带
immediate
形式的防抖函数。
节流: 在指定时间内多次触发事件只执行一次(适用于连续的事件, 比如 scroll
)
function throttle(fn, time) {let preTime = 0return () => {const remainTime = time - (Date.now() - preTime)if (remainTime <= 0) {fn()preTime = Date.now()}}}