-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JS-函数 #99
Comments
闭包了解闭包,先了解函数调用栈,执行上下文 一、作用域闭包概念太抽象了。先学习下作用域。控制着变量的可见性和生命周期。 1. JS是基于词法作用域的,并且只有:
2. 变量声明提升:函数作用域里所有的变量在函数体内都可以见的,JS引擎在编译代码时会把变量声明全部提前到函数体顶部。 (function() {
console.log(a) // undefined
var a = 1;
console.log(a) // 1
})() 3. 作用域链(scope chain):
4. 变量解析:函数体内变量查找的过程:函数体内引用变量时会沿着作用域链逐个向上查找,直到找到,或者未找到(抛ReferenceError,这个跟原型找属性行为不一样)。即函数内可以访问外部作用域里的变量。 二、什么是闭包?理解闭包首先要理解作用域,作用域链和变量解析过程。
综上构成闭包的三个条件:
三、为什么需要闭包?编码中我们经常需要在内存中持久化变量。局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。 四、一些应用场景有函数的地方就有闭包
五、缺点
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype = {
getName: function() {
return this.name;
},
getMessage: function() {
return this.message;
}
}; 所以尽量只在必须时,才使用闭包。 参考 |
练习1. call 和 apply 的区别是什么,哪个性能更好一些
/**
* A faster alternative to `Function#apply`, this function invokes `func`
* with the `this` binding of `thisArg` and the arguments of `args`.
*
* @private
* @param {Function} func The function to invoke.
* @param {*} thisArg The `this` binding of `func`.
* @param {Array} args The arguments to invoke `func` with.
* @returns {*} Returns the result of `func`.
*/
function apply(func, thisArg, args) {
switch (args.length) {
case 0: return func.call(thisArg);
case 1: return func.call(thisArg, args[0]);
case 2: return func.call(thisArg, args[0], args[1]);
case 3: return func.call(thisArg, args[0], args[1], args[2]);
}
return func.apply(thisArg, args);
} 2. 请写出如下代码的打印结果function Foo() {
Foo.a = function() {
console.log(1)
}
this.a = function() {
console.log(2)
}
}
Foo.prototype.a = function() {
console.log(3)
}
Foo.a = function() {
console.log(4)
}
Foo.a(); // 1
const obj = new Foo();
obj.a(); // 2
Foo.a(); // 3 3. 什么是防抖和节流?有什么区别?如何实现?4. memorize函数。所谓记忆函数就是相同的输入,直接返回结果,不用再执行一次函数。有几点注意:
实现1:只缓存最新都调用case function memorize(func, isArgumentsEqual) {
var lastThis;
var lastArgs;
var lastResult;
function isArgumentsEqualDefault(lastArgs, currentArgs) {
if(!lastArgs || !currentArgs || lastArgs.length !== currentArgs.length) {
return false;
}
var length = lastArgs.length;
for(var i = 0; i < length; ++i) {
if(lastArgs[i] !== currentArgs[i]) {
return false;
}
}
return true;
}
// 如果没指定比较函数,则取默认的
if(isArgumentsEqual == null) {
isArgumentsEqual = isArgumentsEqualDefault;
}
return function memorized() {
if(this === lastThis && isArgumentsEqual(lastArgs, arguments)) {
return lastResult;
}
// 先调用,
lastResult = func.apply(this, arguments);
// 如果正常执行函数,则再缓存`this`和`arguments`
lastArgs = arguments;
lastThis = this;
return lastResult;
}
}
function add(a, b) {
console.log(`this.name=${this.name}`)
return a + b;
}
var memorizedAdd = memorize(add);
console.log(memorizedAdd(1,2))
console.log(memorizedAdd(1,2))
var a = {
name: 'john'
}
// 修改this
console.log(memorizedAdd.call(a, 1,2))
console.log(memorizedAdd.call(a, 1,2)) 类似的库memoize-one 实现2:缓存所有调用case
非要实现的话只能根据实际情况选择的合适的方式了。最好还是使用“只缓存最新都调用case”。 |
防抖和节流一、防抖(debounce)指定时间内,方法只能执行一次,多余的事件直接忽略掉(underscore, lodash库的debounce都是这样做的)。而这个时间的计算,是从最后一次触发监听事件开始算起。
1.2 基于概念实现:实现 function debounce(cb, delay) {
var timeoutId;
return function debounced() {
// 每次调用都清除上次的延迟,然后重新创建延迟
timeoutId&& clearTimeout(timeoutId);
timeoutId= setTimeout(() => {
cb.apply(this, arguments);
}, delay)
}
} 1.3 各种实现中增加一些额外功能:
两者计算delay时间的方式是一样的,唯一的区别就是触发调用函数的时间点。
带有"取消操作"和“前缘”的防抖方法(非一次写出,多次优化的结果): function debounce(cb, delay, immediate) {
var timeoutId;
// cancel操作,只依赖timeoutId,所以应避免cancel函数放在debounced函数里声明(这回导致每次调用debounced都会重新声明定义cancel)
var cancel = debounced.cancel = function cancel() {
if(timeoutId) {
clearTimeout(timeoutId);
timeoutId= null;
}
}
function debounced() {
var args = arguments;
var self = this;
function action() {
cb.apply(self, args);
}
// 利用timeoutId标记是否已经调用了cb
if(immediate && !timeoutId) {
action();
}
// 清除上一个delay
cancel();
/*
* 开启新的delay
* immediate=true表示delay取消操作,否则delay函数执行。
*/
timeoutId= setTimeout(immediate ? cancel : action, delay)
}
return debounced;
} 上面实现存在个问题,它是利用 function sleep(delay) {
var pre = Date.now();
while(Date.now() - pre < delay) {}
}
var debounced = debounce((a, b) => {
console.log(`a+b=${a+b}`)
return a + b;
}, 100, true)
// Case1
debounced(1, 23)
sleep(delay) // 同步方式延迟,导致`debounced`里的`setTimeout(immediate ? cancel : action, delay)`回调函数不会被执行
debounced(1, 24) // 虽然时间过去600ms了,但是还是被忽略掉了。
// Case2
debounced(1, 25)
setTimeout(() => {
debounced(1, 26) // 改成异步,就可以触发了
}, 600) 不过这样的实现相对简单清晰,并且绝大部分情况不会存在问题。 二、节流(throttle)节流本质是为了降低函数执行的频率。 舍弃和保留*:
underscore, lodash库的 应用场景: 2.1 基于概念的实现function throttle(fn, delay) {
let previous = 0;
return function throttled() {
const now = Date.now();
const remain = delay - (now - previous);
if(remain <= 0) {
previous = now;
fn.apply(this, arguments);
}
}
}
2.2 改进:增加
|
Function
一、概述
一般通过函数字面量创建函数:
构成的原型链关系:
Function.prototype
),并且该属性含有constructor属性,属性值指向本身。创建一个函数的大致流程是这样的(结合上例):Function.prototype
是所有函数的原型。Object
也是函数,它的原型也是Function.prototype
。内置的对象
Date
,String
,Regexp
,Array
等也都是函数,同样原型都是Function.prototype
。二、定义
2.1 字面量:声明方式
函数声明提升
见JS-ES6-let/const & 变量生命周期
语句块里声明函数
看到两个问题:
总结下来就是:
但是如果存在命名冲突,则规则就比较凌乱了,看看上面引用的资料。
函数作用域只在块作用域里,并且发生声明提升也局限在块作用域里
2.2 字面量:定义表达式
2.3
Function
构造函数/工厂函数很少遇到把,主要用于创建动态函数。
Function()
等价于new Function()
Function
构造函数创建的函数执作用域是全局作用域。三、函数调用方式
3.1 额外变量
函数执行时会有两个额外的变量:
1.
this
:函数执行的上下文,根据调用方式不同取值逻辑不同。
2.
arguments
:函数调用实参列表(伪数组),一般通过
Array.prototype.slice.call(arguments)
转成数组。3.2 四种调用方式
1. 函数方式
this
取值逻辑undefined
, 非严格模式下是全局对象2. 方法方式
this
取值逻辑即为调用的对象3.
new
方式this
取值逻辑参考new方式创建对象只有是构造函数才可以通过
new
方式调用,否则抛TypeError
异常。class
成员方法,静态成员方法Function.prototype
Symbol
函数其实内置函数对象都不可以作为构造函数的,除非显示的说明。并且不可以作为构造函数的内置函数都没有
prototype
属性的。ecma-262: Built-in Function Objects
如何判断一个函数是否可以作为构造函数 ?
没有内置的方法判断,可以利用
new
调用是否抛异常判断:这个方式是存在缺点的,会额外调用
f
函数,可能会造成副作用。所以如果一个函数如果不支持new
方式构造,最好函数内部增加判断,针对new方式调用报错。如何判断一个函数正在通过
new
方式调用 ?4.
apply/call
方式this
变量四、
Function
APIFunction
本身就是个构造函数,用于创建函数对象的,也就是说Function
是所有函数(除了Function.prototype
)的构造函数。4.1
Function.prototype.apply(thiArg[, argsArray])
留意从 ECMAScript 第5版开始argsArray可以是伪数组。
4.2
Function.prototype.call(thisArg, thisArg, arg1, arg2, ...)
主要区分
apply
既然两者之间的差别这么小,为什么要同时提供这两个函数?
call
, 后有apply
;4.3
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
bind
方法和apply/call
类似,但有着本质的区别,bind
方法不会执行函数,只是返回一个绑定指定this
和参数柯里化的新函数(bind的两大功能)。如何写
bind
方法的polyfill ???首先罗列下
bind
的功能:this
变量【必须实现】;2.1 这里隐含一个功能即: 偏函数化后的bound函数
length
属性的值也跟着变化,不过功能很少用。【非必须】new
方式调用,并且当new
方式调用bound函数时,忽略指定的this
变量;【可以不实现】prototype
属性也在new
方式调用创建的对象的原型链上。这侧面说明了bound函数的prototype
属性是原函数的实例。【可以不实现】实现:
了解
bind
的功能后就可以写polyfill了,当然了功能越复杂,性能就越差,所以有时候只实现常用功能也是不错的选择。版本1:
只实现绑定this和偏函数(一般用于回调函数绑定this)
版本2:
增加
new
方式调用处理。不过没有实现更新bound函数的
length
属性值。这个功能太少用了,如果非要实现得借助eval
或则Function
动态创建函数了。版本3:完全实现
npm function-bind已经提供完整的解决方案。
五、研究
Function.prototype
属性Function.prototype
属性作为所有函数的原型,具有一些特殊的特性。Function.prototype
也是个函数,并且是个空函数(即也没返回值)Function.prototype
函数没有实现内部函数[[Construct]]
,也没有prototype
属性,instanceof
的右值六、深度概念
参考
The text was updated successfully, but these errors were encountered: