继承
回顾:面向对象的特点
对象之间的继承
对象拷贝
var laoli = {
name:"laoli",
age:55,
house:"别墅"
};
var xiaoli = {
name:"xiaoli",
age:23
}
function extend(parent,child){
for(var k in parent){
if(child[k]){
continue;
}
child[k] = parent[k];
}
}
extend(laoli,xiaoli);
console.log(xiaoli);
构造函数的属性继承
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
function Student(name,age,sex,score){
Person.call(this,name,age,sex);
this.score = score;
}
var stu = new Student("ww",12,"女",99);
console.dir(stu);
console.log(stu.name);
构造函数的原型方法继承
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi = function (){
console.log("Hello");
}
function Student(name,age,sex,score){
Person.call(this,name,age,sex);
this.score = score;
}
for(var k in Person.prototype){
if(k === "constructor"){
continue;
}
Student.prototype[k] = Person.prototype[k];
}
var stu = new Student("ff",23,"女",67);
stu.sayHi();
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi = function (){
console.log("Hello");
}
function Student(name,age,sex,score){
Person.call(this,name,age,sex);
this.score = score;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
var stu = new Student("ff",23,"女",67);
stu.sayHi();
组合继承
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi = function (){
console.log("Hello");
}
function Student(name,age,sex,score){
Person.call(this,name,age,sex);
this.score = score;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
var stu = new Student("ff",23,"女",67);
stu.sayHi();
函数定义的方式
函数声明
function fn (){
console.log("fn");
}
函数表达式
var fun = function (){
console.log("fun");
}
new Function
- 不推荐:它的性能比较低,js在执行的时候,先把整个字符串解析,然后分析语句结束的位置,在执行。
var func = new Function('var a = "1";console.log(a)');
注意(存在兼容问题):
- 如果将fn()写在最后面,高版本会输出true,因为执行到里就会发现,是个函数声明而不是变量,修改后,根据if条判断输出。
- 低版本,不论if是true还是false,都只会输出false,因其只认最后一次定义的函数。
- 代码展示
/ 在高版本浏览器中,报错
fn();
if(true){
function fn (){
console.log("true");
}
}else {
function fn (){
console.log("false");
}
}
函数声明与函数表达式的区别
- 函数有无名字
- 函数声明:必须有名字
- 函数表达式:可以没有名字,例如匿名函数
- 预解析阶段
- 函数声明:会函数提升,在预解析阶段就已创建,声明前后都可以调用。
- 函数表达式:类似于变量赋值。没有函数提升,在执行阶段创建,必须在表达式执行之后才可以调用。
函数也是对象
- 函数本身也是一种对象,可以通过构造函数方法定义,并调用属性和方法。
var func = new Function('var a = "1";console.log(a)');
函数的调用和this
函数的调用方法
普通函数
function fn (){
console.log("Hi");
}
fn();
构造函数
function Person(name){
this.name = name;
}
var p = new Person("zs");s
对象方法
var o = {
sayName : function (){
console.log("wm");
}
}
o.sayName();
事件函数
document.onclick = function (){
alert("你好");
}
定时器、延时器的函数
setInterval(function (){
console.log("wa");
},1000);
setTimeout(function (){
console.log("timeout");
},1000);
函数内this指向的不同场景
- 普通函数:内部的this默认指向window
- 构造函数:内部的this指向的是将来创建的实例对象
- 对象方法:内部的this默认指向的是调用的对象自己
- 事件函数:指向触发事件函数的事件源
- 定时器、延时器:默认内部的this指向的是window
- 注意:this的指向不是一开始就定死的,而是需要去查看上下文
call、apply、bind方法
- 函数内部在调用时,this有自己默认的指向。但是可以通过下面三个方法来改变this的指向。
call方法
apply方法
- apply() 方法调用一个函数, 第一个参数是一个指定的 this 值,第二个参数是以一个数组(或类似数组的对象)形式提供的参数。
- 注意:该方法的作用和 call() 方法类似,只有一个区别,就是 call() 方法接受的是若干个参数的列表,而 apply() 方法接受的是一个包含多个参数的数组。
- 语法:
fun.apply(thisArg, [argsArray])
function fun (a,b){
console.log(this);
console.log(a + b);
}
var o = {
name : "zs"
}
fun.apply(o,[4,8]);
bind方法
- bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的 call 属性)。
- 参数
- thisArg:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用 new 操作符调用绑定函数时,该参数无效。
- arg1, arg2, ...:当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
- 返回值:返回由指定的 this 值和初始化参数改造的原函数拷贝。
- 演示代码
function fun (a,b){
console.log(this);
console.log(a + b);
}
var o = {
name : "zs"
}
var fn = fun.bind(o,1,34);
console.log(fn);
fn();
fn(6,8);
function fun (a,b,c,d){
console.log(this);
console.log(a + b + c + d);
}
var o = {
name : "zs"
}
var fn = fun.bind(o,1,34);
console.log(fn);
fn(5,3);
call、apply、bind方法的应用
call方法的应用
var arr = [1,5,6,7];
var o = {
0:1,
1:5,
2:7,
length:3
};
Array.prototype.push.call(o,7);
console.log(o);
apply方法的应用
- 数组中没有的一些方法,可以通过apply方法把自己展开,再进行下一步的操作。
- 演示代码
var arr = [1,8,4,7];
console.log(Math.max(arr));
var maxX = Math.max.apply(Math,arr);
console.log(maxX);
- 注意:Math.max.apply(Math,arr)中apply方法的第一个参数,填null或者Math都可以,null表示不改变原来的指针指向。
- 演示代码2
var arr = [1,8,4,7];
console.log(1,5,7,34);
console.log(arr);
console.log.apply(null,arr);
bind方法的应用
var o = {
name : "zs",
age : 13,
s : function (){
setInterval(function (){
console.log(this.age);
}.bind(this),1000);
}
};
o.s();
document.onclick = function (){
console.log(this);
}
document.onclick = function (){
console.log(this);
}.bind(o);
总结
call和apply特性是一样的
- 相同点:
- 1.都是用来调用函数,而且是立即调用
- 2.可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向.
- 不同点:
- call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递.
- apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递.
- 注意: 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window
var arr = [5,9,3,7];
console.log.apply(null,arr);
console.log.apply(window,arr);
console.log.apply(console,arr);
console.log.apply(window.console,arr);
bind方法
- 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
- 不同点: 和 call、apply 最大的区别是:bind 不会调用
- bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
- 1.在 bind 的同时,以参数列表的形式进行传递
- 2.在调用的时候,以参数列表的形式进行传递
- 那么 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准?
- 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部.
函数的其他成员
arguments 实参集合
- 实际应用中,会在函数内部直接使用一个arguments的关键字
- 存储的是函数在调用时,传入的所有实参组成的一个类数组对象.
- 灵活使用arguments类数组对象
- 因为传的参数是不固定的,没有办法通过abcd来确定,就可以使用这种方法来制作.
- 演示代码
js function max(){ var nowMax = arguments[0]; for(var i = 0;i < arguments.length; i++){ if(arguments[i] > nowMax){ nowMax = arguments[i]; } } return nowMax; }
arguments.callee 函数本身,arguments的一个属性
arguments.length 实参的个数
fn.caller 函数的调用者,如果再全局调用,返回的调用者为null
fn.length 形参的个数
fn.name 函数的名称
function fn(a,b){
console.log(fn.arguments);
console.log(fn.caller);
console.log(fn.length);
console.log(fn.name);
console.log(fn.arguments.callee);
}
fn(1,2,3,4);
高阶函数
函数可以作为参数
function eat(movie){
console.log("吃完饭");
movie();
}
function movie (){
console.log("看电影");
}
eat(movie);
函数可以作为返回值
function outer(n){
return function inner(m){
console.log(m + n);
}
}
var fun = outer(100);
fun(8);
fun(18);
函数闭包
回顾作用域、作用域链、预解析
- 温习
- 作用域链: 一个变量在使用时,会优先从自己所在层作用域查找变量,如果当前层没有变量定义会按照顺序从本层往外依次查找,直到找到第一个变量定义(如果一直没有找到,那么就会在控制台报错 is no defined.)
- 遮蔽效应:最近的会遮盖较远的
- 全局作用域
- 函数作用域
- 没有块级作用域
- 内层作用域可以访问外层作用域,反之不行
什么是闭包
- 闭包让你可以在一个内层函数中访问其外层函数的作用域。在js中,每创建一个函数,闭包就会在函数创建的同时被创建出来.
闭包
- 函数定义时天生就能记住自己生成的作用域环境和函数自己,将它们形成一个密闭的环境,这就是闭包。不论函数以任何方式在任何地方进行调用,都会回到自己定义时的密闭环境进行执行。
- 如下图

观察闭包
- 从广义上来说,定义在全局的函数也是一个闭包,只是我们没办法将这样的函数拿到更外面的作用域进行调用,从而观察闭包的特点。
- 闭包是天生存在的,不需要额外的结构,但是我们为了方便观察闭包的特点,需要利用一些特殊结构将一个父函数内部的子函数拿到父函数外部进行调用,从而观察闭包的存在。
- 演示代码
function outer (){
var a = 10;
return function inner (){
console.log(a);
};
}
console.log(a);
var out = outer();
out();
闭包的用途
- 可以在函数外部读取函数内部成员
- 让函数内成员始终存活在内存中
function outer(n){
return function inner(m){
console.log(n + m);
}
}
var out = outer(100);
out(4);
out(7);
out(45);
闭包的问题
var arr = [];
for(var i = 0;i <= 10; i++){
(function (i){
arr[i] = function (){
console.log(i);
}
})(i);
}
arr[0]();
arr[1]();
补充
- 控制台打断点方式(并且可以通过scope查看作用域)
