目录
一、函数
(一)函数的定义方式
1、函数声明方式function关键字(命名函数)
2、函数表达式(匿名函数)
3、new Function()
var fn = new Function('参数1', '参数2', ..., '函数体')
- Function里面参数都必须是字符串格式
- 第三种方式执行效率低,也不方便书写,因此使用较少
- 所有函数都是Function的实例(对象)
- 函数也属于对象
(二)函数的调用方式
1、普通函数 fn()
2、对象的方法 obj.fn()
3、构造函数 new Star()
4、绑定事件函数 点击按钮
5、定时器函数 定时结束
6、立即执行函数(IIFE) 自主执行
(三)函数内this的指向
这些this的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this的指向不同
调用方式 | this指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象 原型对象里面的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
(四)修改函数内this指向
JavaScript为我们专门提供了一些函数方法来帮我们更优雅地处理函数内部this的指向问题,常用的有bind()、call()、apply()三种方法
1、call方法
call()方法调用一个对象,简单理解为调用函数的方式,但是它可以改变函数的this指向。
fn.call(thisArg, arg1, arg2, ...)
2、apply方法
apply()方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。
/**
* fn.apply(thisArg, [argsArray])
* thisArg:在fu函数运行时指定的this值
* argsArray:传递的值,必须包含在数组里面
* 返回值就是函数的返回值,因为它就是调用函数
* */
var o = {
name: 'Bob'
};
function fn() {
console.log(this);
};
fn.apply(o);
// 主要应用:进行数组的一些操作
var arr = [1, 66, 3, 98, 4];
var result = Math.max.apply(null, arr); //调用Math库中的方法,this设为null,将数组作为参数传递进去
console.log(result);
3、bind方法
bind()方法不会调用函数,但能改变函数内部的this指向
/**
* fn.bind(thisArg, arg1, arg2, ...)
* thisArg:在fu函数运行时指定的this值
* arg1, arg2:传递的其他参数
* 返回值就是指定的this值和初始化参数改造的原函数拷贝
* */
// 1基础
var o = {
name: 'Bob'
};
function fn() {
console.log(this);
}
var fun = fn.bind(o); //只改this,不调用函数,返回一个改完this的拷贝函数
fun() //调用拷贝函数
// 2运用
var btn = document.querySelector('button');
btn.onclick = function () {
this.disabled = true;
setTimeout(function () {
this.disabled = false; //由于定时器中的this指向的是window,而此处需要的this是btn的this,因此需要利用bind将定时器内部的this指向改为外部this的指向
}.bind(this), 2000);
}
(五)高阶函数
高阶函数是对其他函数进行操作的函数,它接受函数作为参数或将函数作为返回值输出。
(例如回调函数)
(六)闭包
1、概念
闭包(closure)指有权访问另一个函数作用域中变量的函数。
简单的理解就是,一个作用域可以访问另外一个函数内部的局部变量。
2、作用
闭包主要用来封装私有变量, 提供一些暴露的接口(让函数外部可以访问函数内部局部变量)
function fn() {
var num = 10;
return function () {
console.log(num);
}
}
var f = fn();
f();
3、实际运用
// 1、利用动态添加属性实现for循环下的事件监听
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i=0; i<lis.length; i++) {
lis[i].index = i;
lis[i].onclick = function () {
console.log(this.index);
}
}
// 2、利用闭包实现上述功能
for (var i=0; i<lis.length; i++) {
// 利用for循环创建了4个立即执行函数,
(function (i) { //接收i
lis[i].onclick = function () {
console.log(i);
}
})(i) // 把函数外部作用域(for里的)i传递进入立即执行函数内
}
(七)递归函数
- 函数内部自己调用自己,这个函数就是递归函数
- 递归里面必须加退出条件(return)
(八)浅拷贝 & 深拷贝
1、浅拷贝概念
浅拷贝只拷贝一层,更深层次对象级别的只拷贝引用
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
for (var k in obj) {
// k是属性名, obj[k]是属性值
o[k] = obj[k];
}
console.log(o);
o.msg = 20;
console.log(o);
console.log(obj);
上述代码中,由于obj内部还有一个对象msg,因此当for循环进行拷贝时,o拷贝到了obj最外面一层的属性,而更深层的对象则会被o拷贝走地址,所有当改变o.msg时,obj下面的msg属性的值也会随着发生改变。(第一层拷贝,后面的引用,就不拷贝了)
2、浅拷贝语法糖
Object.assign(target, ...sources) es6新增方法可以浅拷贝
eg: Object.assign(o, obj);
3、深拷贝概念
深拷贝拷贝多层,每一级别的数据都会拷贝
每一层都拷贝(利用递归)
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['black', 'white']
};
var o = {};
// 封装函数
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {
// 判断属性值属于哪种类型
// 1、获取属性值 oldobj[k]
var item = oldobj[k];
// 2、判断这个值是否是数组
if (item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k], item);
}
// 3、判断这个值是否是对象
else if (item instanceof Object) {
newobj[k] = {};
deepCopy(newobj[k], item);
}
// 4、属于简单数据类型
else {
newobj[k] = item;
}
}
}
deepCopy(o, obj);
console.log(o);
二、原型与原型链
(一)函数的prototype
1、函数的prototype属性
每个函数都有一个prototype属性,它默认指向一个Object空对象(即称为:原型对象)//找爹
原型对象中有一个属性constructor, 它指向(构造)函数对象 //找儿子
2、给原型对象添加属性(一般都是方法)
作用:函数的所有实例对象自动拥有原型中的属性(方法)
(二)显示原型与隐式原型
1、每个函数function都有一个prototype,即显示原型(属性)
function Fn(){} Fn.prototype
2、每个实例对象都有一个__proto__,可称为隐式原型(属性)
var fn = new Fn() fn.__proto__
3、实例对象的隐式原型的值等于对应构造函数的显示原型的值
Fn.prototype = fn.__proto__
4、内存结构
function Fn() { //内部语句:this.prototype = {}
}
console.log(Fn.prototype);
var fn = new Fn(); //内部语句:this.__proto__ = Fn.prototype
console.log(fn.__proto__);
console.log(Fn.prototype === fn.__proto__); //true
// 给原型添加方法
Fn.prototype.test = function () {
console.log('test()');
}
fn.test()
5、总结:
函数的prototype属性:在定义函数时自动添加的,默认值是一个空Object对象
对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值
程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前)
(三)原型链
当访问对象的一个属性或方法时,它会先在自身中寻找,如果没有,向上去父级原型对象中寻找,再没有去祖父级,最多可向上寻找两级,即寻找到祖父级,最终没找到会返回undefined。
别名:隐式原型链
作用:查找对象的属性(方法)
(四)探索instanceof
用途:判断左边的对象是不是右边对象的实例(前者是否属于后者)
表达式:A instanceof B
如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false
(五)原型对象的this指向问题
在构造函数和原型对象中,里面的this指向的都是对象实例
(六)原型对象的应用
扩展内置对象法(为构造函数的原型对象添加方法)
console.log(Array.prototype);
Array.prototype.sum = function () {
var sum = 0;
for (var i=0; i<this.length; i++) {
sum += this[i];
}
return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum());
三、继承
ES6之前并没有给我们提供extends继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
1、call()
调用这个函数,并且修改函数运行时的this指向。
fun.call(thisArg, arg1, agr2, ...)
thisArg: 当前调用函数this的指向对象
agr:需要传递的参数
function fn(x, y) {
console.log('hello world');
console.log(this);
console.log(x+y);
}
var o = {
name: 'Bob'
};
// 1、调用函数:fn和fn.call效果相等
fn();
console.log('------------')
fn.call();
console.log('------------')
// 2、改变函数的this指向:将o作为参数传进call即将fn的this指向改为了o,同时还能传递参数
fn.call(o, 3, 4)
2、借用构造函数继承父类型属性
核心原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
// 借用父构造函数继承属性
// 父构造函数
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
// 子构造函数
function Son(uname, age) {
Father.call(this, uname, age) // 先调用父构造函数,然后再通过call将父构造函数中的this改为子构造函数的this,从而实现继承
}
var son = new Son("小明", 18);
console.log(son);