JavaScript高级第二天
01-定义函数的三种方式
1.函数声明 function:可以先调用再声明,因为预解析(把函数声明、变量声明进行提升)
function fn() {
//函数体
conle.log(1);
}
2.函数表达式:不可以先调用再声明,因为预解析只是把变量声明提升了,赋值留在原位。
var fn2 = function() {
conle.log(2);
}
fn(2);
3.函数也是对象,函数也是通过构造函数new出来的 Function(大写的)(了解,工作当中不会使用到)
语法:
var fn3 = new Function(arg1, arg2, arg3..., bodyFn);
参数:
1.参数的个数是若干个
2.参数的类型都是字符串类型的
3.除了最后一个参数bodyFn外,其他参数都是创建的函数的形参
4.最后一个参数bodyFn是创建的函数的函数体
例:
var fn4 = new Function('n1', 'n2', 'alert(n1 + n2)');
// 等价于以下代码:
// var fn4 = function(n1, n2) {
// alert(n1 + n2);
// }
02-函数的原型链
function Person(){}
// 底层是通过 Function new 出来的
// var Person = new Function(); //Person是实例对象,是函数
Person的原型链:
Person ==> Function.prototype ==> Object.prototype ==> null
Function.prototype常用成员
- call:调用函数,重新指向this
- apply:调用函数,重新指向this
- bind:重新指向this,返回一个新的函数,不调用。
03-完整版原型链
核心的点:函数的双重身份,函数是函数,也是对象
1.函数作为构造函数来使用去创造对象
function Person(){
}
var p = new Person();
绘制原型三角关系:
- 构造函数:Person
- 原型对象:Person.prototype
- 实例对象:p
2.把函数当实例看,函数也是构造函数 Function 创建
底层就是 var Person = new Function();
绘制原型三角关系:
- 构造函数:Function
- 原型对象:Function.prototype
- 实例对象:Person
3.把Object考虑进来 ==> 当构造函数来使用
var obj = new Object();
绘制原型三角关系:
- 构造函数:Object
- 原型对象:Object.prototype
- 实例对象:obj
4.把Object当成实例对象来使用
任何函数底层都是 Function 创建的,都是Function的实例对象
底层 var Object = new Function();
绘制原型三角关系:
- 构造函数:Function
- 原型对象:Function.prototype
- 实例对象:Object
5.Function 当实例对象来看,谁把 Function 创建出来的
var Function = new Function();
绘制原型三角关系:
- 构造函数:Function
- 原型对象:Function.prototype
- 实例对象:Function
绘制完整版原型链的目的是辅助大家理解js中对象的继承关系:
小结:
-
任何对象的原型链上都有Object.prototype
-
任何函数的原型链上都有 Function.prototype
-
函数是一等公民,
- typeof 函数==> function
- Function.prototype ==> 原型对象的类型是个函数
- Function 自己生自己
-
函数的双重身份
是函数,也是对象
是函数,有prototype属性
是对象,有 _ _ proto_ _ 属性
所以说函数有prototype属性,也有 _ _ proto_ _ 属性
练习题1:(看图就可以完全答出来)
Function.prototype === Function.prototype true
Object.__proto__ === Function.prototype true
Function.prototype.__proto__ === Object.prototype true
Object.prototype.__proto__ === Object.prototype false
Object.__proto__.__proto__ === Object.prototype true
04-练习题
function Person() {}
var p = new Person()
console.log(p.constructor) // Person
console.log(Person.constructor) // function
05-instanceof
instanceof:实例对象
语法:
对象 instanceof 构造函数
作用:
- 字面上理解:对象是否是构造函数的实例对象,如果是的,就返回 true
- 从原型链角度来理解:
- 构造函数的prototype属性是否在对象的原型链上,如果在 返回true,否则返回false
练习题
06-作用域
- 变量可以起作用的区域(就是说一个变量声明定义好了,就可以在哪些区域内使用)
- 在js中,有全局作用域和函数作用域(词法作用域,静态作用域)
函数作用域:当函数声明定义好了,其函数作用域就定下来了,其作用域链也定下来了
作用域链:任何函数可以形成作用域,函数嵌套在另外一个函数中,那么外层函数也有自己的作用域,这样从里到外直到全局作用域,形成的链式结构叫做作用域链。
每一个函数的作用域链:都是从当前函数作用域出发,从里到外形成
07-变量的搜索原则
沿着作用域链来查找
- 首先会在当前作用域内查找是否有声明该变量,如果有就返回变量的值
- 如果没有,就去外层作用域中查找
- 如果外面也没有,就沿着作用域链继续往外找,直到全局作用域,如果有就返回
- 如果还没有,报错。(属性值找不到就是undefined,变量找不到就报错)
08-练习题
09-函数的四种调用模式
分析this指向的问题:
- 任何函数都有属于自己的this指向。
- this指向是动态灵活的,只有当函数调用的时候,才能确定下来this的指向
- 如何去分析this指向:
- 看这个this属于哪个函数
- 看这个函数是如何调用的 ==> 就能决定下来this指向谁
函数的四种调用模式:
第一种:函数调用模式
这种调用模式,函数内的this指向window。
第二种:构造函数调用
new + 构造函数():这种调用模式,构造函数的this指向新创建出来的实例对象
第三种:方法调用模式
对象.方法名():这种调用模式,方法内的this指向调用方法的那个对象
谁调用,this指向谁
是点语法和中括号语法,都是方法调用模式
练习题:
function foo() {
conle.log(this); //arr
}
var arr = [10, 20, foo];
// 方法调用模式
arr[2]();
第四种:上下文调用模式 ==> call()、apply()、bind() 这三个方法
10-练习题
// 1.
var age = 38;
var obj = {
age: 18,
getAge: function () {
console.log(this.age);
}
}
var f = obj.getAge; //仅仅是把getAge方法赋值给了变量f
// 函数调用模式,函数内的this,this指向window
f();//38
// 2.
var age = 38;
var obj = {
age:18,
getAge:function () {
// 方法内的this指向obj
console.log(this.age);//18
function foo(){
// 任何函数都有属于自己的this
// foo内的this和getAge方法内的this不是一个,没有关系
console.log(this.age);//38
}
// 函数调用模式
foo();
}
}
// 方法调用模式,
obj.getAge();
obj["getAge"]();
// 3.
var length = 10
function fn() {
console.log(this.length) // 10 3
}
var obj = {
length: 5,
method: function (fn) {
// 函数调用模式 fn函数内的this指向window
fn() // 10
// arguments 伪数组:实参列表 ==> 类似于这样[fn, 10, 5]
// 方法调用模式,fn这个方法被arguments调用
arguments[0]();
}
}
obj.method(fn, 10, 5);
几种特殊的this指向
- 定时器中的this指向了window,因为定时器的function最终是由window来调用的。
- 事件中的this指向的是当前的元素,在事件触发的时候,浏览器让当前元素调用了function
11-上下文调用模式
作用:可以自己去取指定this指向
call()、apply()、bind() 三个方法,任何函数都可以去使用以上三个方法
1. call()方法:可以用来调用函数
1.除了小括号可以调用函数外,还可以使用call()调用函数
演示:
function fn() {
conle.log(111);
}
fn();
fn.call();
2.call的第一个参数可以用来修改函数内的this指向
function fn(2) {
conle.log(2);
conle.log(this);
}
fn2(); // 2 window
fn.call([10, 20, 30]); // 2 [10, 20, 30]
3.call的参数是若干个
第一个参数 ==> 用来修改this指向
除了第一个参数之外的所有其他参数 ==> 都是用来给函数传递实参的
function fn3(n1, n2) {
conle.log(this);
conle.log(n1 + n2);
}
// fn3(10, 20)
fn3.call([], 100, 200); //
作用:
- 调用函数
- 第一个参数来改this指向
- 给函数传递实参 ==> 除第一个以外的所有参数
12-方法借用(调)模式
上下文调用模式又称方法借用(调)模式
13-伪数组与数组
伪数组与数组最大的区别:伪数组不能使用数组的方法,但可以让伪数组去借用数组的方法
伪数组定义:是个对象,有数字下标还有length属性,所以可以和数组一样进行循环,但是伪数组不能使用数组的方法。
常见的伪数组:
- arguments实参列表
- querySelectorAll()、getElementByTagName() 获取到的元素
- jq对象
实例:
// 需求:给obj伪数组添加 2: '波哥'
var obj = {
0: '班班',
1: '大飞哥',
length: 2
}
// 这是常规做法(第一种方法)
// obj[2] = '波哥';
// 还需要手动维护length
// obj.length++;
// conle.log(obj);
// Array.prototype.push:是从数组中的原型对象中去拿到push
// [].push ==> 从空数组上去获取到push方法
// 第二种:
// 数组的原型对象:
// Array.prototype.push.call(obj, '波哥'); //效果等价于:obj.push('波哥')
// 第三种:
;[].push.call(obj, '波哥', '啦啦', 'kuqi');
conle.log(obj);
注意点:
-
[]。push 简化写法,一定要注意: {} 和 [] 一定要使用
;
分号隔开,否则语法报错 -
数组的push方法不仅有添加功能,内部还会自动的去维护length属性
14-伪数组借用数组的方法
var obj = {
0: '班班',
1: '大飞哥',
2: '哥哥'
length: 3
};
把obj伪数组里面的每一项拼接成字符串 '班班-大飞哥-哥哥'
var str = [].join.call(obj, '-');
console.log(str);
15-apply方法
call方法和apply方法作用是一样的,写法上有区别
一样的参数:
- 可以调用函数
- 可以修改this指向
- 可以给函数传递实参
apply的语法
apply(thisArg, [实参列表])
- thisArg用来修改函数内的this指向
- 实参列表:是个数组或者伪数组,里面的就是传递给函数的所有实参
演示:
function fn() {
console.log(1);
}
fn();
fn.call();
fn.apply();
function fn2() {
console.log(2);
console.log(this);
}
fn2.call({name: 'call'});
fn2.apply({name: 'apply'});
function fn3(n1, n2) {
console.log(this);
console.log(n1 + n2);
consloe.log(arguments);
}
fn3.call([], 1, 2);
fn3.apply([10, 20], [30, 50]);
apply的平铺性:apply会把第二个参数里面的每一项取出来作为实参给函数传递过去。
闭包
闭包的基本概念
闭包(closure)是JavaScript语言的一个难点,也是JavaScript的一个特色,很多高级的应用都要依靠闭包来实现。
闭包的概念
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数
在JavaScript中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则产生闭包。
产生闭包的条件
当内部函数访问了外部函数的变量的时候,就会形成闭包。
闭包的作用
保护私有变量不被修改
计数器
需求:统计一个函数的调用次数
var count = 0;
function fn(){
count++;
console.log("我被调用了,调用次数是"+count);
}
fn();
fn();
fn();
缺点:count是全局变量,不安全。
使用闭包解决这个问题!!!!
function outer(){
var count = 0; // 私有变量, 将count保护起来了
function add(){
count++;
console.log("当前count"+count);
}
return add;
}
var result = outer();
result();
斐波那契数列优化
缓存(cache):数据的缓冲区,当要读取数据时,先从缓冲中获取数据,如果找到了,直接获取,如果找不到,重新去请求数据。
计算斐波那契数列,会有很大的性能问题,因为重复的计算了很多次,因此我们可以使用缓存来解决这个性能问题。
初级优化:
使用缓存的基本步骤:
- 如果要获取数据,先查询缓存,如果有就直接使用
- 如果没有,就进行计算,并且将计算后的结果放到缓存中,方便下次使用。
//缓存
var arr = [];
var fbi = function (n) {
count++;
if (n == 1 || n == 2) {
return 1;
}
if (arr[n]) {
return arr[n];
} else {
var temp = fbi(n - 1) + fbi(n - 2);
arr[n] = temp;//存入缓存
return temp;
}
}
缺点:既然使用缓存,就需要保证缓存的数据的安全,不能被别人修改,因此,需要使用闭包来实现缓存的私有化。
function outer() {
//缓存
var arr = [];
var fbi = function (n) {
if (n == 1 || n == 2) {
return 1;
}
if (arr[n]) {
return arr[n];
} else {
var temp = fbi(n - 1) + fbi(n - 2);
arr[n] = temp;//存入缓存
return temp;
}
}
return fbi;
}
var fbi = outer();
console.log(fbi(40));
闭包经典面试题
打印下标
继承
现实生活中的继承,子承父业, 如儿子继承了父辈的财产,公司等
程序中的继承,一个对象可以使用另一个对象中的方法或属性
继承的目的: 方便代码的复用
var lw = {
skill: "翻墙",
age: 28
}
function Person(){}
var xm = new Person();
// 如何实现让xm可以使用到lw对象上的skill属性???
xm.skill; // ==> 翻墙
JS常见的几种继承模式
原型链继承
一个对象可以访问构造函数的原型中的属性和方法,那么如果想要让一个对象增加某些属性和方法,只需要把这些属性和方法放到原型对象中即可。这样就实现了继承, 称之为原型链继承
- 直接给原型增加属性和方法
- 原型替换(注意:constructor)
借用构造函数继承
从构造函数中继承到构造函数中的成员
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
function Chinese(name, age, gender, skin){
this.name = name;
this.age = age;
this.gender = gender;
// 以上代码重复,在构造函数Person中已经给this添加
this.skin = skin;
}
// 重写构造函数Chinese
function Chinese(name, age, gender, skin){
// 借用Person构造函数,继承到name、age、gender属性
Person.call(this, name, age, gender);
this.skin = skin;
}
组合继承
借用构造函数 + 原型链继承组合在一起使用
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
}
// Person原型上的sayHi方法
Person.prototype.sayHi = function(){
console.log("hello, 我是" + this.name);
}
function Chinese(name, age, gender, skin){
// 借用Person构造函数,继承到name、age、gender属性
Person.call(this, name, age, gender);
this.skin = skin;
}
var xm = new Chinese("xm", 20, "male", "黄色");
// xm有name/age/gender属性从Person构造函数中继承到的
// 那么如何让Chinese的实例对象xm去继承到Person原型上的sayHi方法???
xm.sayHi();