function fn () {}
var fun = function () {}
new Function()
构造函数 new Function("参数1","参数2",...,"函数体")`
注意:
验证:
var f = new Function('a','b','console.log(a+b)');
f(1,2);
// 所有函数都是Function的实例(对象)
console.dir (f);
console.log (f instanceof Object); // 返回True
关键字instanceof
的作用:判断前者(f
)属不属于后者(Object
)
所以,这里得出一个重要的结论:函数也属于对象。
也再一次印证了 js 里面的万物皆对象。
function fn() {
console.log('普通函数')
}
//调用方法
fn(); // 或者 fn.call()
var o = {
sayHi:function() {
console.log('对象方法中的函数')
}
}
// 调用方法
o.sayHi()
function Star() {}
// 调用方法
new Star();
// 点击了按钮即可调用此函数
btn.onclick = function(){};
// 定时器自动调用
setInterval(function(){},1000); // 自动每隔 1 秒调用一次
// 写法:()();
(function(){
console.log('立即执行函数');
})();
// 立即执行函数是自动调用
这些this
的指向,是当我们调用函数的时候确定的,调用方式的不同决定了this
的指向不同,一般指向我们的调用者。
this
指向出现的多种情况:
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象、原型对象里的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
function fn() {
console.log('普通函数的this:' + this);
}
fn(); // 完整的写法:window.fn(); 即,指向函数的调用者
var o = {
sayHi: function() {
console.log('对象方法的this:' + this);
}
}
o.sayHi(); // 指向的是对象o
// 构造函数:指向ldh这个实例对象,原型对象里的this 指向的也是ldh这个实例对象
function Star() {};
Star.prototype.sing=function(){
}
var ldh= new Star();
// 4. 绑定事件函数
var btn = document.querySelector('button');
btn.onclick = function() {
console.log('绑定时间函数的this:' + this)
}; // 点击了按钮就可以调用这个函数
绑定事件函数this
指向的是函数的调用者btn这个按钮对象
结果输出:
// 定时器函数
setInterval(function() { // 完整的写法是 window.setInterval
console.log('定时器的this:' +this);
}, 1000);
// 立即执行函数
(function() {
console.log('立即执行函数的this:' + this);
})();
立即执行函数 this
还是指向的window
。
结果输出:
JavaScript 为我们专门提供了一些函数 方法来帮我们更优雅的处理函数内部this的指向问题。常用的有bind()
、call()
、apply()
三种方法。
call()
方法可以调用一个函数。简单理解为调用函数的方式,它可以改变函数的 this
指向。
应用场景: 一般应用于继承.。
语法规范:
fun.call(thisArg,arg1,arg2,...)
示例:
// 改变this 指向
var o = {
name:'andy'
}
function fn() {
console.log(this);
};
fn.call(o);
call 的作用:1)可以调用函数;2)可以改变函数内的this指向;3)可以实现继承(主要作用)。
apply()
方法可以调用一个函数。简单理解为调用函数的方式,它也可以改变函数的 this 指向。
apply 一般应用于操作数组数据。
应用场景::经常跟数组有关系
比如:求最大值、最小值。
由于数组没有求最大值的方法(只有数学对象里面才有Math.max()
),我们可以利用apply
,借助于数学内置对象求最大值。
语法规范:
fun.apply(thisArg,[argsArray])
thisArg
:在fun函数运行时指定的this
值;argsArray
:传递的值,必须包含在数组里面;var o = {
name: 'andy'
}
function fn(arr) {
console.log(this);
console.log(arr) // 输出的是字符串 'blue'
};
fn.apply(o,['blue'])
apply
可以改变函数内部this指向,并立即调用函数;[]
)。// 求最大值
var arr = [1,66,3,99,4];
// var imax = Math.max.apply(null,arr); // apply调用数学中的求最大值的方法。null 表示不改变this指向
// 参数 arr 传递给max
var imax = Math.max.apply(Math,arr); // this 重新指向Math
console.log(imax) // 输出 99,成功找到最大值
bind()
方法不能调用函数,但是能改变函数内部this
指向,返回的是原函数改变this
指向之后产生的新函数;
应用场景
如果有的函数我们不需要立即调用,但又想改变这个函数内部的this
指向时。
语法结构:
fun.bind(thisArg,arg1,arg2...)
thisArg
:在fun
函数运行时指定的this值;arg1
、arg2
:传递的其他参数;this
值和初始化参数改造的原函数拷贝。bind 的基本使用
var o = {
name:'andy'
}
function fn(a,b){
console.log(a + b);
}
var f=fn.bind(o,1,2);
f();
bind
的应用
点击按钮,禁用3秒后开启
var btn= document.querySelector('button');
btn.onclick = function() {
this.disabled = true; // this指向的是btn按钮
setTimeout(){
this.disabled = false; // 定时器函数里的this指向的是window
}.bind(this),3000) // 这个this因为写在计时器外面,所以它指向的是btn这个对象
}
相同点:
都可以改变函数内部的this指向。
不同点:
call
和 apply
可以调用函数,并且改变函数内部this
指向;call
和 apply
传递的参数不一样,call
传递的参数是aru1
,aru2
形式,而 apply
必须是数组形式[arg]
;bind
不能调用函数,但可以改变函数内部this指向。主要应用场景:
call
:经常做继承;apply
:经常跟数组有关系,比如借助于数学对象Math.实现查找数组最大值和最小值。bind
:如果不想调用函数,但是又想改变this指向时,比如改变定时器内部的this
指向。在不同应用场景,合理选用这3个方法。
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode),ES5 的严格模式具有限制性,它是 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。(比如原先的变量可以不声明直接赋值)
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class
,enum
,export
, extends
, import
, super
不能做变量名。
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分 为为脚本开启严格模式和为函数开启严格模式两种情况。
有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
为整个脚本文件开户严格模式,只需要在所有语句之前放一个特定语句"use strict"; (或 ‘use strict’;),单引号、双引号均可。
<script>
"use strict"; //开启了严格模式
...
</script>
因为 " use strict "加了引号,所以老版本浏览器会把它当作一行普通字符串而忽略。
严格模式要求比较多,比较规范,如果担心自己驾驭不了,可以仅为某一个函数开启严格模式。
要给某个函数开启严格模式,需要把“use strict”; (或 ‘use strict’; ) 声明放在函数体所有语句之前。
function fn(){
"use strict";
return "123";
}
//当前fn函数开启了严格模式
严格模式对 Javascript 的语法和行为,都做了一些改变。
变量规定
delete X;
语法是错误的this指向问题
this
指向window
对象;this
指向undefined
;this
指向全局对象;new
调用,由于this指向的是undefined
,如果给它赋值,则会报错;new
实例化的构造函数指向创建的对象实例;this
仍是指向window
for (var i=0; i<5;i++) {
function f2() {} // !!! 语法错误
}
function fn () {
function fun(){} // 合法
}
另外,严格模式也不允许有8进制
更多严格模式要求参阅:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
高阶函数:是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
函数可以当做参数来进行传递,回调函数
<script>
function fn(callback) {
callback && callback();
}
fn(function(){alert('hi')});
</script>
<script>
function fn() {
return function(){};
}
fn();
</script>
此时 fn
就是一个高阶函数。
函数,也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
实例:
回调函数是高阶函数的一个运用
// 高阶函数- 函数可以作为参数传递
function fn(a, b, callback) {
console.log(a + b);
callback && callback(); // 有callback传入进来,才调用 callback()
}
fn(1, 2, function() {
console.log('我是最后调用的');
});
补充说明:
实例中的callback && callback()
语句,表示如果callback
不存在,就不执行函数调用。如果 运算符是“||
”,则不管callback
存不存在都调用, 不存在就报错。
运算符
&&
的作用,保持符号两边都为真,一个不为真就停止后面的脚本。
JavaScript 中的 闭包 和 异步,并称两大难点。
变量根据作用域的不同分为两种: 全局变量和局部变量
闭包:指有权访问另一个函数作用域中变量的函数。
——《JavaScript高级程序设计》
或简单理解为,一个作用域可以访问另外一个函数内部的局部变量。
例如:
// fun这个函数作用域,访问了另外一个函数fn里面的局部变量num
function fn(){
var num = 10;
function fun(){
console.log(num);
}
fun();
}
fn();
以上代码,就产生了闭包,而变量所在的函数fn
,就是闭包函数。
在断点调试Scope 里,可清晰看到产生的过程
闭包的主要作用:延伸了变量的作用范围。
代码验证:
// fn外面的作用域也可以访问fn内部的局部变量(利用了return)
function fn(){
var num = 10;
return function(){
console.log(num);
}
}
var f = fn();
f();
HTML结构
<ul class="nav">
<li>榴莲li>
<li>臭豆腐li>
<li>鲱鱼罐头li>
<li>大猪蹄子li>
ul>
JavaScript 代码
1)用以前的方法获取当前li
的索引号
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) { // for是同步任务
lis[i].index = i;
lis[i].onclick = function() { // function是异步任务
console.log(this.index);
}
}
2)利用闭包的方式获得当前li
的索引号(本案例面试中常用)
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
(function(i) { // ②然后在这里传入变量"i"
lis[i].onclick = function() {
console.log(i);
}
})(i); // ①首先在这里传入变量 "i"
}
方式2 )使用立即执行函数(function(){})()
产生了闭包。
但这种方法在本案例中,反而不如方式1)高效,且容易内存泄漏(如果一直不点击,那么变量i
就不会被销毁)。
立即执行函数也叫小闭包
要求:3 秒种之后,打印所有li
元素的内容。
HTML结构
<ul class="nav">
<li>榴莲li>
<li>臭豆腐li>
<li>鲱鱼罐头li>
<li>大猪蹄子li>
ul>
JavaScript 代码
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) { // for是同步任务
(function(i) { // 先创建一个立即执行函数
setTimeout(function() { // 是异步任务
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
异步任务,主要有3种情况:
1)回调函数;
2)定时器中的回调函数;
3)事件中的回调函数。
要求:
JavaScript 代码
<script>
var car = (function() {
var start = 13; // 起步价(局部变量)
var total = 0; // 总价 (局部变量)
return {
// 正常的费用
price: function(n) { // n:公里数
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5
}
return total;
},
// 拥堵之后的费用
yd: function(flag) {
return flag ? total + 10 : total;
}
}
})();
console.log(car.price(5));; // 假设是5公里(n)
console.log(car.yd(true)); // true:有拥堵情况
</script>
递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己,这个函数就是递归函数。
<script>
// 递归函数:函数内部自己调用自己
function fn(){
fn();
}
fn();
</script>
递归函数的作用和循环效果一样;
由于递归很容易发生“栈溢出”错误(stack overflow
),所以必须要加退出条件return
。
<script>
// 递归函数:函数内部自己调用自己
var num =1;
function fn(){
console.log('递归');
if(num==6){
return; // 递归里面必须加退出条件
}
num ++ ;
fn();
}
fn();
</script>
<script>
// 利用递归函数求1~n的阶乘
function fn(n) { // 用户输入几,就求 1~n之间的阶乘
if (n == 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3)); // 假设用户输入3
</script>
代码解析:
假如用户输入的是3,
1)由于 if (n==1)
判断为false
,因此不执行return 1
。而去执行后面的n * fn(n-1)
,传入3后的返回值为 3 * fn(3 - 2)
。即return 3 * fn(2)
;
2)计算fn(2)
fn(2)
相当于再次调用了fn
的函数,那么此时里面的fn
,传递的就是2了。相当于
return 3 * (2 * fn(2 - 1)); // 即f(2) 等价于 3 * (2 * fn(2 - 1))
fn(2 - 1)
即 fn(1)
,亦即n =1。此时,经过判断 if(n==1)
,就执行return 1;
,
return 3 * (2 * 1) // <=> return n * fn(n - 1)
// 最后,返回6
fn(2)
计算过程:
//return 3 * fn(2)
//return 3 * (2 * fn(1))
//return 3 * (2 * 1)
//return 3 * (2)
//return 6
总结:
我们利用递归函数,求了1~n之间的阶乘。
其主要思路是,不管是求1 ~ n
之间的阶乘,还是求1~n之间的累加和,都是用我们的前一项(n
)乘以后一项(n - 1
)。只不过,后一项需要用函数来重新执行一次;
递归函数还必须要加一个判断条件,和一个退出条件。如果正好处于第1项,就直接把第1项返回即可,不需要再进行阶乘或累加和。
分析:
n
的前面两项(n-1
、n-2
),就可以计算出n
对应的序列值。JavaScript 代码:
<script>
// 利用递归求斐波那契数列(免子序列)1、1、2、3、5、8、13、21...
// 用户输入一个数字n 就可以求出这个数字对应的免子序列值。
function fb(n) {
if (n === 1 || n === 2) { // 给递归函数加一个退出的判断条件
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(4)); // 输出 3
console.log(fb(8)); // 输出 21
</script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱'
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
案例要求:根据上面创建的对象,要求输入 id 后,返回对应的数据对象
思路:
利用 forEach
去遍历里面的每一个对象。
forEach:可以遍历数组并得到数据中的每个元素。
这里采用封装函数来实现:
function getID(json, id) {
// 1、利用forEach遍历里面的每个数组对象
json.forEach(function(item) {
//console.log(item); // 返回2个数组元素
if (item.id == id) {
console.log(item);
// 2、获取最里层的数据对象11、12利用递归函数
}
});
}
console.log(getID(data, 1));
但是,上面这个forEach
只能得到最外层的两个id
为1
和2
的两个对象。而对于里层的两个id
分别为11
和12
的对象却得不到。
这时,要想获取里层的11
、12
,就可以把forEach或这个函数再执行一次,那么就利用到了递归函数。
function getID(json, id) {
json.forEach(function(item) {
//console.log(item); // 返回2个数组元素
if (item.id == id) {
console.log(item);
// 利用递归得到里层的数据11、12
} else if (item.goods && item.goods.length > 0) {
getID(item.goods, id); //递归函数
}
});
}
getID(data, 1);
getID(data, 11); // 输出 11对应的数据对象
从表面看,这里没有专门设置递归的退出条件。但是,由于使用了if …else…做执行递归的条件,因此不再需要单独的退出设置。
至此,已能打印出筛选后的数据对象。但需要实现的是将筛选得到的值返回,而不是打印,以给我们在某些情况下使用。因此,需要先保数据保存出来。
需要声明一个空对象 o ,用于保存筛选完成后的数据,完整函数如下:
function getID(json, id) {
var o = {};
json.forEach(function(item) {
//console.log(item); // 返回2个数组元素
if (item.id == id) {
// console.log(item);
o = item;
return item;
// 利用递归得到里层的数据11、12
} else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id);
}
});
return o;
}
console.log(getID(data, 1));;
console.log(getID(data, 11));;
ES6浅拷贝语法:
Object.assign(*target,...sourcer*)
target
:目标对象(拷贝给谁);sourcer
:拷贝对象(拷贝谁)。 var obj= {
id:1,
name:'andy'
};
var o={};
1)、利用原生 JavaScript 实现把对象obj
的每一项拷贝给对象o
思路:用for...in
来遍历 obj
这个对象,把每一项拿出来给o
for (var k in obj) {
// k 是属性名;obj[k]是属性值
o[k]=obj[k] // 赋值操作
}
console.log(o);
2)、利用ES6新增的浅拷贝方法
Object.assign(o,obj);
console.log(o);
实际开发中更 推荐 assign
方法实现浅拷贝。
<script>
var obj={
id:1,
name:'andy',
msg:{
age:18
}
}
var o={};
</script>
由于有多层数据,所以用递归更合适:先遍历外层数据(id
、name
),再遍历里层数据(msg
)
封装函数实现深拷贝
function deepCopy(newObj, oldObj) {
for (var k in oldObj) {
// 判断属性值属于简单数据类型还是复杂数据类型
// 1、先拿到值才能判断——获取属性值 oldObj[k]
var item = oldObj[k];
// 2、判断这个值是否是数组;
if (item instanceof Array) {
newObj[k] = []; // 相当于 o.color=[]
deepCopy(newObj[k], item)
} else if (item instanceof Object) {
// 3、判断这个值是否是对象
newObj[k] = [];
deepCopy(newObj[k], item)
} else {
// 4、属于简单数据类型
newObj[k] = item;
}
}
}
deepCopy(o, obj) // 函数调用
console.log(o);
上一篇:JavaScript从入门到放弃 -(四)E5 新增方法
下一篇:JavaScript从入门到放弃 -(六)正则表达式