banner
NEWS LETTER

I Know JS 函数篇

Scroll down

JS函数

函数传参

需要注意的是,JS中的函数传参都是按值传递的,这意味着不管参数是基本数据类型还是引用数据类型,函数外传进去的值都会被复制到函数内部,对于引用类型,可以理解为:函数内创造了一个自己的指针指向传入的这个引用类型参数

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 第一段代码
function setName(person) {
person.name = 'tim'
}
const p = new Object();
setName(p);
console.log(p.name); // tim

// 第二段代码
function setName(person) {
person.name = 'tim';
person = new Object();
person.name = 'bob';
}
const pp = new Object();
setName(pp);
console.log(pp.name); // tim

从上述例子可以看出,函数内部的person并不是我们传入的pp对象本身,而是函数新创造的一个和pp指向同一片内存的指针。一旦person被重新赋值,将无法再修改传入的参数。

模板字面量标签函数

模板字面量支持自定义标签函数,标签函数本身是一个普通函数,通过前缀到模板字面量来应用自定义行为

1
2
3
4
5
function tag(strings, ...expressions) {
console.log(strings);
console.log(expressions);
}
tag`${name} is ${age} years old`; //

数组函数

Array.from

是一个静态方法,从 可迭代对象类数组对象【带有length属性和索引元素的对象】 创建一个新的浅拷贝 的数组实例

参数有三个:

  • Iterable:可迭代对象或类数组对象
  • ?mapFn:可选参数,每个将要添加到数组的值会首先传递给该函数,该函数接受两个参数: element正在处理的元素和index 索引
  • ?thisArg:可选参数,是执行mapFn时用的this

例子

1
2
3
4
5
// 从字符串生成数组
Array.from('foo'); // ['f', 'o', 'o']
// 生成一个数字序列,因为数组元素初始化的值时undefined,所以下列的v是undefined
Array.from({length: 5}, (v, i) => i); // [0,1,2,3,4]

待理解

from() 方法可以在任何构造函数上调用,只要该构造函数接受一个表示新数组长度的单个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function NotArray(len) {
console.log("NotArray called with length", len);
}

// 可迭代对象
console.log(Array.from.call(NotArray, new Set(["foo", "bar", "baz"])));
// NotArray called with length undefined
// NotArray { '0': 'foo', '1': 'bar', '2': 'baz', length: 3 }

// 类数组对象
console.log(Array.from.call(NotArray, { length: 1, 0: "foo" }));
// NotArray called with length 1
// NotArray { '0': 'foo', length: 1 }

this不是一个构造函数,返回一个普通数组对象

1
2
console.log(Array.from.call({}, {length:1,0: "ff"}))
// ['foo']

转换异步的可迭代对象到数组,可使用Array.fromAsync()方法

Array.prototype.flat

flat方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

1
2
const arr = [1,2,3,[2,3,4,[5,6]], 8];
console.log(arr.flat(2)); // output

Array.prototype.indexOf()、lastIndexOf()、index()使用严格相等算法(即===),需要注意的是 NaN == NaN 为false
Array.prototype.includes() 是一个零值相等算法(零值相等不作为API公开,详见下一节笔记),NaN值视作相等

函数防抖和节流

通过定时器来减少不必要的函数调用频率,得到更好的用户体验和性能。指定的行为在停止指定时间后,再执行对应任务,

一个频繁执行的函数,在规定的时间内,只在最后一次触发执行,场景:输入框提示语,避免每次输入都发送请求;登录、发送按钮,用户点击太快,发送多次请求。关键在于清零,比如在点击登录后1s再发送请求,设置一个定时器,用户每次操作都会清零这个定时器,只有用户停止操作规定时间才会发送请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 防抖函数
* fn 执行函数
* delay 延迟时间
*/
function debounce(fn, delay){
let timer = null;
return function() {
clearTimeout(timer);
timer=setTimeout(()=>{
fn.call(this)
}, delay)
}
}

一个频繁操作的函数,在执行过一次后,只有经过规定的时间才会执行第二次,在规定时间内,函数只会执行一次,场景:每隔1s更改一次播放进度;每隔1s计算一次scroll高度,需要两个时间 lastTime 和 nowTime 来计算时间差 由此来判断是否执行事件 先将lastTime初始化为0 然后获取系统时间 做差判断是否大于delay 如果大于则执行事件并将nowTime赋予lastTime 由此完成节流。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
*节流函数
*/
function throttle(fn, interval) {
let start = 0;
return function() {
let now = new Date();
if(now-start > interval) {
fn.apply(this);
start=now;
}
}
}

函数(柯里化)currying

把接受多个参数的函数变化成接受一个单一参数(最初函数的第一个参数)的函数,返回一个接受剩下参数的新函数

1
2
3
4
5
6
7
8
9
10
11
//curring前
function fn(x,y) {
return x+y;
}
//curring后
function(x) {
return function(y) {
return x+y;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
curring化的好处在于:
1. 参数复用
2. 延迟执行
function sayKey(key) {
console.log(this[key])
}
const person = {
name: 'name',
age: 23
}
// call不是科里化
sayKey.call(person, 'name') // 立即输出 name
sayKey.call(person, 'age') // 立即输出 23

// bind是科里化
const say = sayKey.bind(person) // 不执行
// 想执行再执行
say('name') // name
say('age') // 23

compose函数

1
2
3
4
5
6
7
8
9
compose = (fn1,fn2) => c =>fn1(fn2(c));
function compose(...func){
return function (x) {
return func.reduce(function(arg, fn){
return fn(arg)
}, x)
}
}
// 从右往左执行时,用reduceRight

function的length

function函数的length等于 第一个具有默认值的参数之前的参数个数,剩余参数(即function(…fn))不参与length计算

1
2
3
4
5
6
function f1(x,y)
f1.length //2
function f2(x=1, y)
f2.length //0
function f3(x, y=2)
f3.length //1

bind/call/apply

这三个函数的功能都是把函数绑定到某个对象,使函数体内的this指向bind/call/apply函数的第一个参数。方法的第一个参数即函数的this指向,即函数f内的this将指向对象{z:1},后面的函数会依次传递给原始函数,即bind的第二个参数3将传递给函数f

  • call的第二第三个函数是依次传递给原始函数的,
  • apply的所有参数放在一个数组里面传递进去
  • bind返回的是一个新函数,参数和call一样是多个,逗号隔开的。
1
2
3
4
5
6
let f=function(x,y){
return this.z+x+y;
}
let m=f.bind({z:1},2,3); //m是新函数
let c=f.call({z:1},2,3);
let a=f.apply({z:1},[2,3])

bind函数的返回值时绑定了this的新函数,它不是立即执行的

【more】
非严格模式下:如果传递的第一个参数是一个基本类型,会被包装成对象;传入null或undefined时会被绑定为全局对象
严格模型下:传啥就是啥

参考文档:https://segmentfault.com/a/1190000002640298

console.log

console.log 会尝试将接收的所有参数转换为字符串,针对不同类型会有不同的处理

  • 对原始类型,它们的toString()方法会被调用,输出相应的字符串表示
  • 对函数对象,取决于不同的js环境
  • 对普通对象,通常不会直接调用

setTimeout 和 setInterval

当传入的第二个参数是0ms,实际的执行时间并不一定会像预想的那样立即实现,具体来说,延时设置为0意味着这个任务被立即放入了微任务队列中,但是这个队列什么时候能执行到这个回调函数,是未知的
HTML5规定:一旦对 setTimeout 的嵌套调用被安排了 5 次,浏览器将强制执行 4 毫秒的最小超时。浏览器内部以 32 位带符号整数存储延时。这就会导致如果一个延时大于 2147483647 毫秒(大约 24.8 天)时就会溢出,导致定时器将会被立即执行。
为了优化后台标签的加载损耗(以及降低耗电量),浏览器会在非活动标签中强制执行一个最小的超时延迟

占位符

1
2
3
// %s %d %i(整数) %f %o %O(obj) %c(css样式) 等占位符在console.log中同样适用
// %c 后跟使用该样式的字符串
console.log("%c hello %c world", 'color: red;background: black', 'color: gold;background: skyblue');

class的静态方法

静态方法被设计为只能被创建它们的构造器使用,不能传递给实例,无法被实例使用。


JS API

toLocaleString

mdn
toLocaleString()返回这个数字在特定语言环境下的表示字符串

语法

1
toLocaleString([locales[,options]]);

参数
locales:可选参数,带有BCP 47语言标记的字符串或字符串数组,用来表示要转为目标语言的类型

options:可选参数,用来配置属性对象。{style: ‘xxx’, }

style可选值 注释
decimal 纯数字格式(默认)
currency 货币格式
percent 百分比格式
unit 单位格式

当options.style === ‘currency’时,需要增加options.currency属性

currency可选值 注释
USD 美元格式
EUR 欧元格式
CNY 人民币格式
  1. 将date对象转成字符串形式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const now = new Date() 
    now.toLocaleString() // "2022/1/12 下午4:59:49"

    const num = 12876431
    num.toLocaleString('zh', {style: 'currency', currency: 'usd'}) // 'US$1,231,124.00'
    num.toLocaleString('zh', {style: 'currency', currency: 'cny', currencyDisplay: 'name'})// '1,231,124.00人民币'

    let arr = [12,'56']
    arr.toLocaleString('zh') //'12,56'

typeof

typeof 操作符会返回变量的基本类型
哪些情况下typeof会返回'undefined'

  1. typeof undefined === 'undefined'
  2. 对一个未声明的变量使用typeof 会返回'undefined',但对处于’暂时性死区‘的变量使用typeof 会抛出ReferenceError
  3. 对未定义的对象属性使用typeof
  4. 未定义的函数参数使用typeof,如果在函数调用时,某个参数未传参,在函数内访问此变量会是undefined

使用Object.prototype.toString.call()检测数据类型

object.prototype.toString()方法返回对象的字符串表示,当调用一个对象的toString()方法时,它会返回一个表示该对象的字符串。这个字符串通常包含对象的类名和内部标识符,可以用来识别对象的类型和内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 判断目标变量的数据类型
var type = function (o){
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};

['Null',
'Undefined',
'Object',
'Array',
'String',
'Number',
'Boolean',
'Function',
'RegExp'
].forEach(function (t) {
type['is' + t] = function (o) {
return type(o) === t.toLowerCase();
};
});

type.isObject({}) // true
type.isNumber(NaN) // true
type.isRegExp(/abc/) // true

Date对象

1
2
3
4
5
// Date对象格式化方法
let now = new Date();
now.toDateString() // ''Tue May 31 2022''
now.toTimeString() // '19:38:21 GMT+0800 (中国标准时间)'

Object.defineProperty

使用Object.defineProperty方法定义的对象属性

  • 默认不可被枚举,即(Object.keys中没有)
  • 默认不可改变
1
2
3
4
5
6
7
8
9
const person = {
name: 'li',
}
Object.defineProperty(person, age, {
value: 18,
enumerable: true, // 将enumerable设置为true,就可以被枚举
writable: true, // 可被修改
});
console.log(Object.keys(person))

createElement

createElementNS

1
document.createElementNS(ns, name);

创建一个带有指定命名空间的元素节点,返回一个element元素

scrollIntoView方法

有三种方式

1
2
3
scrollIntoView()
scrollIntoVuew(alignToTop) // 可传true或false,true表示滚动到和顶端对齐,false表示和底端对齐。只能设置垂直方向上的滚动
scrollIntoView(scrollIntoViewOptions)

scrollIntoViewOptions包含3个属性:behavior, block, inline

behavior定义滚动行为。可选值:

  • **smooth**: 平滑滚动
  • **instant**:直接跳转
  • **auto**:默认值。由css属性scroll-behavior 决定

block定义垂直方向的对齐方式,可选值

  • **start**:默认值。与滚动容器顶部对齐
  • **center**:与滚动容器居中对齐
  • **end**:与滚动容器底部对齐
  • **nearest**:在视野范围内就不滚动,否则滚动到顶部或底部中较近的那个

inline定义水平方向的对齐方式,和block一样,可选值:

  • **start**:元素左侧和滚动容器左侧对齐
  • **center**:元素和滚动容器居中对齐
  • **end**:元素右侧和滚动容器右侧对齐
  • **nearest**:默认值。如果已经在视野范围内,就不滚动,否则就滚动到左边或者右边(哪个更靠近就滚到哪里)

使用CSS属性scroll-margin可设置scrollIntoView滚动距离边界的边距。

scrollIntoViewIfNeeded方法可做到只需要在滚动定位的时候才会滚动,和nearest类似,但同时又满足了center。兼容性如下:

字符串类型的replace函数

接受两个参数:

  • 第一个是一个RegExp对象或者一个字符串,当传递字符串时只会匹配第一个子串,传递正则表达式并携带全局标记时可替换所有子串。
  • 第二个参数是一个字符串或一个函数。当传递字符串时,可以用几个特殊的字符序列来插入正则表达式操作的值
字符序列 替换文本
$$ $
$& 匹配整个模式的子字符串
$' 匹配的子串之前的字符串
$` 匹配的子串之后的字符串
$n 匹配的第n个捕获组的字符串,$n>=0;n<=9$
$nn 匹配的第nn个捕获组的字符串
当传递的是一个函数时,如果只有一个匹配项,这个函数接受三个参数:
match:对整个正则匹配项而言,模式匹配的字符子串
pos:匹配项在整个字符串中的开始位置
originalText:以及整个字符串。
如果有多个匹配项,在原来三个参数的基础上,还会把对每一个匹配项匹配到的字符串传进去,假设有n个匹配项参数会变成:
  • match 匹配整个正则的子串
  • p1 第一个匹配项捕获到的部分
  • p2 第二个匹配项捕获到的部分
  • pn 第n个匹配项捕获到的部分
  • pos
  • originalText
    参数的最后两个始终是模式匹配的开始位置和原始字符串

console

==console.log/error/warning==
日志/错误/警告打印

1
console.log({name, age, grade});

==console.time + console.timeEnd==
计算代码段执行时间

==console.assert断言==

1
console.assert(false, 'is false')

==console.dir==
打印dom节点

1
console.dir($0);

其他文章