JavaScript入门②-函数(1)基础{浅出}
<p><img src="https://img2022.cnblogs.com/blog/151257/202211/151257-20221114172950676-36532104.png" alt="image.png" loading="lazy"></p><p><strong>JavaScript入门系列目录</strong></p>
<ul>
<li>JavaScript入门①-基础知识筑基</li>
<li>JavaScript入门②-函数(1)基础{浅出}</li>
<li>JavaScript入门③-函数(2)原理{深入}执行上下文</li>
<li>JavaScript入门④-万物皆对象:Object</li>
<li>JavaScript入门⑤-欲罢不能的对象、原型与继承</li>
<li>JavaScript入门⑥-WEB浏览器API</li>
<li>JavaScript入门⑦-DOM操作大全</li>
<li>JavaScript入门⑧-事件总结大全</li>
<li>JavaScript入门⑨-异步编程●异世界之旅</li>
<li>JavaScript入门⑩-ES6归纳总结</li>
</ul>
<h1 id="01js函数基础">01、JS函数基础</h1>
<h2 id="11函数定义">1.1、函数定义</h2>
<p><strong>函数</strong>(方法)就是一段定义好的逻辑代码,函数本身也是一个<code>object</code>引用对象。三种函数构造方式:</p>
<p><strong>🔸① 函数申明</strong>:<code>function 函数名(参数){代码}</code>,申明函数有函数名提升的效果,先调用,后申明(和var申明提升类似,比var提升更靠前)。</p>
<p><strong>🔸② 函数表达式</strong>:<code>var func = function(参数){代码}</code>,定义变量指向函数,函数不需要命名。不过也可以像申明函数一样指定函数名,在其内部调用自己。</p>
<p><strong>🔸③ Function构造函数</strong>:<code>new Function(参数,代码)</code>,支持多个构造参数,前面为函数参数,最后一个为函数体,使用不多。</p>
<pre><code class="language-javascript">function func1(a) {
console.log(a);
}
var func2 = function(b){
console.log(b);
}
var func3 = new Function('c','console.log(c)');
//调用函数
func1(1);
func2(2);
func3(3);
</code></pre>
<blockquote>
<p><strong>❗注意</strong>:JS中没有方法重载,不允许相同的函数名,重名会被覆盖。</p>
</blockquote>
<p><strong>🔸return 返回值</strong>:</p>
<ul>
<li>通过<code>return</code> 返回值,并结束方法。</li>
<li>无<code>return</code>,则默认返回<code>undefined</code>。</li>
</ul>
<h2 id="12参数argument">1.2、参数argument</h2>
<ul>
<li><strong>参数可以不传</strong>,则为<code>undefined</code>,也可多传,没卵用(也不一定,<code>arguments</code>参数数组可以用)。</li>
<li><strong>参数不可同名</strong>,如果同名,则后面为准。</li>
<li><strong>形参与实参</strong>:函数定义的参数<code>a</code>为形参,调用时传入的数据<code>3</code>为实参。</li>
<li><strong>参数设置默认值</strong>几种方式:形参赋默认值(ES6)、参数验证赋值。</li>
</ul>
<pre><code class="language-javascript">function func1(a="默认值") { //一般推荐的方式
a=a?a:"默认值"; //参数为null、undefined、0、false、空字符值都会用默认值
a=a||"默认值";//同上
console.log(a);
}
</code></pre>
<ul>
<li><strong>参数的值传递和引用传递</strong>:取决于参数的类型,值类型传递副本,引用类型传递对象指针地址,操作的是同一个对象。这里需要理解JS里面的值类型、引用类型的基本原理。</li>
</ul>
<pre><code class="language-javascript">function f1(n) {
n += 1;
}
let n = 100;
f1(n);//值传递:传入的是n的值副本,不影响原本n值
console.log(n); //100 n没有变
function f2(obj) {
obj.n += 1;
}
let nobj = { n: 100 };
f2(nobj); //引用传递,传入的是nobj地址指针,操作同一对象,对象是共享的
console.log(nobj);//{n: 101}
</code></pre>
<ul>
<li><strong>arguments</strong>:函数传入的实参都保存在<code>arguments</code>数组对象中,对于任意数量参数的方法就很有用。</li>
</ul>
<pre><code class="language-javascript">function sum() {//求和
let n = 0;
for (let i = 0; i < arguments.length; i++) {
n += arguments;
}
return n;
}
sum(1,2,3,108,594); //求和,支持任意个参数
</code></pre>
<ul>
<li><strong>剩余参数</strong><strong>(...theArgs)</strong>:把不确定的参数放到一个<strong>数组</strong>里,前面是确定参数,最后一个形参以<code>...</code>开头表示其他<strong>剩余参数</strong>数组,既然是数组,当然就可以支持任意数量的参数了。</li>
</ul>
<pre><code class="language-javascript">// 连接字符串,支持任意数量字符参数
function connect(separator, ...sargs) {
let str = "";
for (let i = 0; i < sargs.length; i++) {
str += sargs?.toString() + separator;
}
return str.slice(0, -separator?.toString().length); //去掉结尾的分隔符
}
connect('--', 1, 2, 3, 'a', 'b', 'c');//1--2--3--a--b--c
</code></pre>
<h2 id="13函数变量作用域">1.3、函数(变量)作用域</h2>
<ul>
<li><strong>局部变量</strong>:函数内的变量称为“局部变量”,函数里才有作用域的问题——不能被全局、其他函数访问。</li>
<li><strong>全局变量</strong>:全局变量可以被自由访问,包括函数内</li>
<li><strong>父函数变量</strong>:函数中定义的函数也可以访问在其父函数中定义的所有变量,和父函数有权访问的任何其他变量。简单来说就是函数变量作用域<strong>单向向下传递</strong>,子级可以访问父级的变量。</li>
</ul>
<pre><code class="language-javascript">var num1 = 0;
function getScore() {
let num1 = 2, //和全局变量同名
num2 = 3;
function add() {
num3 = 5; //没用任何申明的局部变量,使用后自动变为隐式全局变量
return num1 + num2; //可以访问全局、父级的变量
}
return add();
}
getScore(); //5
console.log(num1, num3); //0 5
</code></pre>
<p><strong>⁉️注意</strong>:</p>
<ul>
<li>全局、局部(方法内)变量同名,两者没有关系,函数内肯定<strong>就近</strong>用自己的了。</li>
<li><strong>没用任何申明</strong>的局部变量,<strong>使用后</strong>自动变为<strong>隐式全局变量</strong>,全局<code>window</code>莫名其妙就有了很多私生子,so,不要这样干!</li>
</ul>
<h2 id="14-箭头函数">1.4、()=>{ }箭头函数</h2>
<p><strong>箭头函数</strong>(IE🚫)是一种简洁的函数表达式,顾名思义就是用箭头<code>=></code>创建函数,它<strong>没有</strong>自己的<code>this</code>、<code>arguments</code>、<code>super</code>。箭头函数总是匿名的,适用于那些需要匿名函数的地方。</p>
<blockquote>
<p><strong>语法规则</strong>:(param1, param2, …, paramN) <strong>=></strong></p>
</blockquote>
<pre><code class="language-javascript">let add1 = (a, b) => { return a + b; };
let add = (a, b) => a + b;//同上,只有一行代码可以省略花括号{} 和 return
let logDate = () => { console.log(new Date()) }; //可以没有参数
let logError = e => { console.log('发生错误:', e) }; //一个参数可以省略括号
</code></pre>
<p><code>=></code>箭头函数和普通函数有什么区别呢?这是考点:</p>
<table>
<thead>
<tr>
<th>区别</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>申明方式不同</td>
<td>普通函数需要<code>function</code>关键字,箭头函数当然就是箭头<code>=></code>了</td>
</tr>
<tr>
<td>没有自己的<code>arguments</code></td>
<td>箭头函数在全局环境中,没有<code>arguments</code>;当箭头函数处于普通函数的中,<code>arguments</code>是上层普通函数的<code>arguments</code></td>
</tr>
<tr>
<td>没有自己的<code>this</code></td>
<td>没有自己的this,其<code>this</code>指向其函数定义的外层作用域环境的<code>this</code>,且不能被call、apply、bind函数改变。</td>
</tr>
<tr>
<td>没有自己的<code>prototype</code></td>
<td>箭头函数没有自己的原型,加上没有自己的<code>this</code>,所以也就不能作为构造函数使用</td>
</tr>
</tbody>
</table>
<pre><code class="language-javascript">// 箭头函数
let f1 = (a, b) => {
console.dir(f1.prototype);//undefined
console.log(arguments); //arguments is not defined
}
// 普通函数,嵌套了一个箭头函数
let f2 = function (a) {
console.dir(f2.prototype);//Object
// 嵌套的箭头函数
let f1 = (a, b) => {
console.log(arguments);
}//是父级f2的arguments
f1(a, 2);
}
//this
var name = '大哥';
let user = {
name: 'sam',
sayHi1: function () {
console.log('Hi', this.name)
},
sayHi2: () => { console.log('Hi', this.name) },
}
user.sayHi1();//Hi sam :this指向调用者
user.sayHi2();//Hi 大哥 :this指向定义的环境,全局对象
// 指定新的this
let nobj={name:'张三'};
user.sayHi1.call(nobj); //Hi 张三 :this指向绑定对象
user.sayHi2.call(nobj); //Hi 大哥 :this指向全局对象,始终没变
</code></pre>
<h2 id="15全局函数">1.5、全局函数</h2>
<table>
<thead>
<tr>
<th><strong>函数</strong></th>
<th><strong>描述</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>eval()</strong></td>
<td>执行JS代码(不推荐):<code>eval("console.log('eval')");</code><br>- <strong>比较危险</strong>,它使用与调用者相同的权限执行代码,字符串代码容易被被恶意修改。<br>- <strong>效率低</strong>,它必须先调用JS解释,也没有其他优化,还会去查找其他JS代码(变量)。<br>- <strong>推荐用 <strong><code>Function()</code></strong> 构造函数代替</strong><code>eval()</code><br></td>
</tr>
<tr>
<td>isNaN()</td>
<td>判断一个值是否是非数值</td>
</tr>
<tr>
<td>parseFloat()</td>
<td>转换字符为浮点数</td>
</tr>
<tr>
<td>parseInt()</td>
<td>转换字符为整数</td>
</tr>
<tr>
<td>decodeURI()</td>
<td>URL解码</td>
</tr>
<tr>
<td>encodeURI()</td>
<td>URL编码</td>
</tr>
<tr>
<td><strong>alert</strong>(str)</td>
<td>弹窗消息提示框</td>
</tr>
<tr>
<td><strong>confirm</strong>(str)</td>
<td>弹窗消息询问确认框,返回boolean</td>
</tr>
<tr>
<td>console</td>
<td>控制台输出</td>
</tr>
<tr>
<td>console.<strong>log</strong>(str)</td>
<td>控制台输出一条消息</td>
</tr>
<tr>
<td>console.<strong>error</strong>(str);</td>
<td>打印一条错误信息,类似的还有<code>info</code>、<code>warn</code></td>
</tr>
<tr>
<td>console.<strong>dir</strong>(object)</td>
<td>打印对象</td>
</tr>
<tr>
<td>console.<strong>trace</strong>()</td>
<td>输出堆栈</td>
</tr>
<tr>
<td>console.<strong>time</strong>(label)</td>
<td>计时器,用于计算耗时(毫秒):<strong>time</strong>(开始计时) > <strong>timeLog</strong>(计时) > <strong>timeEnd</strong>(结束)</td>
</tr>
<tr>
<td>console.<strong>clear</strong>()</td>
<td>清空控制台,并输出 Console was cleared。</td>
</tr>
</tbody>
</table>
<pre><code class="language-javascript">let arr = eval(''); //转换字符串为数组
let jobj = eval("({name:'sam',age:22})");//转换字符串为JSON对象
let jobj2 = new Function("return {name:'sam',age:22}")(); //转换字符串为JSON对象
//计时time,需一个统一标志
console.time("load");
console.timeLog("load"); //load: 5860ms
console.timeLog("load"); //load: 18815ms
console.timeEnd("load"); //load:25798 毫秒 - 倒计时结束
</code></pre>
<hr>
<h1 id="02函数调用callapplybind">02、函数调用/call/apply/bind</h1>
<p><strong>常用的函数调用方式</strong>:</p>
<ul>
<li>直接函数名调用:<code>函数名(参数...);</code></li>
<li>对象调用:对象里的函数,<code>对象.函数名(参数...);</code></li>
<li>递归调用,嵌套调用自身,须注意退出机制,避免死循环,代码的世界没有天荒地老。</li>
</ul>
<p><strong>不常用函数调用方式</strong>:call/apply/bind 调用函数都可以指定<code>this</code>值。</p>
<table>
<thead>
<tr>
<th><strong>属性/方法</strong></th>
<th><strong>描述</strong></th>
<th><strong>语法</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>call</strong>()</td>
<td>调用函数,指定<code>this</code>、参数</td>
<td><code>function.call(thisArg, arg1, arg2, ...)</code></td>
</tr>
<tr>
<td><strong>apply</strong>()</td>
<td>调用函数,指定<code>this</code>、参数数组</td>
<td><code>function.apply(thisArg, argsArray)</code></td>
</tr>
<tr>
<td><strong>bind</strong>()</td>
<td>绑定(复制)一个函数,指定<code>this</code>、参数</td>
<td><code>function.bind(thisArg, arg1, arg2, ...)</code></td>
</tr>
</tbody>
</table>
<h3 id="call">🔸call()</h3>
<p>通过 Function.prototype.<strong>call</strong>() 调用一个函数,调用语法:</p>
<blockquote>
<p>function.<strong>call</strong>(thisArg, arg1, arg2, ...)</p>
</blockquote>
<ul>
<li>第一个参数<code>thisArg</code>指定运行时<code>this</code>,当第一个参数为null、undefined的时候,默认指向window。这一点可用来实现函数的<strong>“继承”</strong>(在构造函数中调用父构造函数)</li>
<li>后面为函数原本的参数。</li>
</ul>
<pre><code class="language-javascript">function sum(n1, n2) {
return n1 + n2;
}
sum(1,2); //正常调回
sum.call(null, 1, 2); //call调用
console.log(Math.max(1, 2)); //正常调回
console.log(Math.max.call(null, 1, 2)); //call调用
</code></pre>
<h3 id="apply">🔸apply()</h3>
<p>通过 Function.prototype.<strong>apply</strong>() 调用函数,调用语法:</p>
<blockquote>
<p>function.<strong>apply</strong>(thisArg, argsArray)</p>
</blockquote>
<p>和<code>call()</code>的唯一区别就是第二个参数是一个参数数组,数组参数会分别传入原函数。</p>
<pre><code class="language-javascript">function sum(n1, n2) {
return n1 + n2;
}
sum.apply(null, ); //apply调用
console.log(Math.max.apply(null, )); //apply调用,数组传入多个参数
//绑定this
var uname = "sam";
let f = function () {
console.log(this.uname);
}
f(); //sam
f.call({ uname: "call" }); //call
f.apply({ uname: "apply" }); //apply
</code></pre>
<h3 id="bind">🔸bind()</h3>
<p>通过 Function.prototype.<strong>bind</strong>() 创建一个<strong>副本函数</strong>:该副本函数绑定了<code>this</code>和参数,且一经绑定,永恒不变(不可更改,不可二次绑定)。</p>
<blockquote>
<p>function.<strong>bind</strong>(thisArg[, arg1[, arg2[, ...]]])</p>
</blockquote>
<pre><code class="language-javascript">function log(type, title, message) {
console.log(`type:${type}, title:${title}, message:${message}`)
}
let errorLog = log.bind(null, '错误');//返回绑定的函数,绑定第一个参数
errorLog('登录发生异常', '超过登录次数');
</code></pre>
<hr>
<h1 id="03函数闭包">03、函数闭包</h1>
<h2 id="31什么是闭包">3.1、什么是闭包?</h2>
<p><strong>闭包</strong>是<strong>函数</strong>和<strong>申明该函数的词法环境</strong>的组合,简单来说能够访问其他函数(通常是父函数)内部变量的函数,加上他引用的外部变量,组成了<strong>闭包</strong>。通常就是嵌套函数,嵌套函数可以”继承“父函数的参数和变量,或者说内部函数包含外部函数的作用域。</p>
<ul>
<li><strong>内部函数+外部引用形成了一个闭包</strong>:它可以访问外部函数的参数和变量。闭包存储了自己和其作用域的变量,这样在函数调用栈上才能使用外部函数的变量。</li>
</ul>
<pre><code class="language-javascript">function FA(x) {
function FB(y) {
function FC(z) {
console.log(x + y + z);
}
FC(3);
console.dir(FC)
}
FB(2);
console.dir(FB)
}
FA(1); //6
console.dir(FA)
</code></pre>
<p><strong>作用域链(C>B>A)</strong>:B和A形成闭包,B可以访问A,保存了A的变量;C和B形成闭包,可以访问B(也包括B有的A作用域),如下图FC函数形成的闭包中存储了FB、FA的变量、参数信息。</p>
<p><img src="https://img2022.cnblogs.com/blog/151257/202211/151257-20221114172950702-644311385.png" alt="image.png" loading="lazy"></p>
<p>因此,闭包就是为了解决了函数的词法作用域问题,FC函数就可以单独使用了。V8引擎是把闭包封装成了一个<code>"Closure"</code>对象,保存函数上下文中的<code>[]</code>集合里。同一个函数多次调用都会产生单独的闭包,如果闭包使用不当或太多,容易引发内存泄漏风险。</p>
<blockquote>
<p>JS设计闭包这个东西,一言难尽!详见后续《函数执行上下文》</p>
</blockquote>
<h2 id="32闭包应用柯里化currying">3.2、闭包应用:柯里化(Currying)</h2>
<p><strong>柯里化</strong>是一种函数的高级玩法,简单来说,就是把多参数的函数<code>f(a,b)</code> ,转换成了另一种函数形式 <code>f(a)(b)</code>。</p>
<pre><code class="language-javascript">//一个普通的日志函数
function print(title, message) {
console.log(title, message);
}
//柯里化转化函数
function curry(func) {
return function (title) {
return function (message) {
return func(title, message);
}
}
}
//柯里化转化
let cprint = curry(print);
//调用
cprint('用户模块')('用户1登录了');
//复用:复用包含了title参数值的函数。
let userPrint = cprint('用户模块');
userPrint('用户1退出登录');
userPrint('用户3打赏了游艇');
</code></pre>
<p>原理其实不难理解,就是利用闭包的(词法作用域)机制,返回多层闭包函数,直到最后一个参数来了才执行。这么做到底有什么好处?——<strong>答案就是复用,复用参数值</strong>。如上面示例中的<code>userPrint</code>,是一个包含了<code>title</code>参数值的(闭包)函数,他还有一个正式的名字,叫<strong>偏函数</strong>。</p>
<p>一个更通用的柯里化实现如下,采用递归的方式,不仅可以生成任意偏函数,还支持正常调用。</p>
<pre><code class="language-javascript">function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
//使用
let log = curry(print);
log('登录模块','系统崩了');//正常调用
let userLog = log('直播模块'); //偏函数-复用
userLog('表演才艺');
userLog('上链接');
</code></pre>
<hr>
<h1 id="04this关键字">04、this关键字</h1>
<p><strong>this</strong> 是JS的关键字,指向<strong>当前执行的环境对象</strong>,允许函数内引用当前环境的其他变量,不同场景指向不同。在严格模式(<code>"use strict";</code>)下又会不同。大多数情况下,函数的调用方式决定了 <code>this</code> 的值(运行时绑定),每次调用函数的<code>this</code>也可能不同。</p>
<pre><code class="language-javascript">this.x1 = 100;
console.log(this.x1); //这里的this指向全局对象window
function User() {
this.sname = "sam";
this.age = 20;
console.log(this.sname, this.age);
}
new User(); //sam 20,User构造函数里的this指向new创建的新User实例
</code></pre>
<blockquote>
<p><strong>this <strong>指向的是一个</strong>对象引用</strong>,不是指向函数自身,也不是指向函数的词法作用域。大多数情况下默认都指向全局<code>window</code></p>
</blockquote>
<ul>
<li><strong>this=全局window</strong>:在全局执行环境中(在任何函数体外部)this 都指向全局对象<code>window</code>。</li>
<li><strong>this=new对象</strong>:构造函数中的<code>this</code>指向其新对象;对象的属性方法中的<code>this</code>指向该对象。</li>
<li><strong>this=调用者</strong>:局部(函数内的)this,谁调用函数,this指的就是谁。<strong>箭头函数</strong>除外,箭头函数本身没有this,也不会接收call、apply的传递,指向其函数定义环境的<code>this</code>,而非执行时。</li>
<li><strong>this=事件元素</strong>:在事件中,this表示接收事件的元素。</li>
<li><strong>this=绑定对象</strong>:<strong>call</strong>(thisArg)、<strong>apply</strong>(thisArg)、<strong>bind</strong>(thisArg)绑定参数<code>thisArg</code>作为其上下文的<strong>this</strong>,若参数不是对象也会被强制转换为对象,强扭的瓜解渴!</li>
<li><strong>this=undefined</strong>:严格模式下,如果this没有被执行环境(execution context)定义,那<code>this</code>就是<code>undefined</code>。</li>
</ul>
<pre><code class="language-javascript">function Foo() {
console.log(this);//调用Foo(),this指向window;如果new Foo()则指向新对象
var fa = () => { console.log("fa:" + this) };
var fb = function () {
console.log("fb:" + this);
}
fa(); //箭头函数,调用Foo(),this指向调用者window;如果new Foo()则指向新对象
fb(); //匿名函数,调用Foo()、构造函数调用,this指向调用者window,
this.fc1 = fa; //属性方法:this指新对象
}
Foo();//调用Foo()函数
new Foo();//构造函数调用创新实例
</code></pre>
<blockquote>
<p>又是一个JS的坑!好像懂了,又好像没懂!详见后续</p>
</blockquote>
<hr>
<blockquote>
<p><strong>©️版权申明</strong>:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!<em>原文编辑地址-语雀</em></p>
</blockquote><br><br>
来源:https://www.cnblogs.com/anding/p/16889713.html
頁:
[1]