前引
我们在了解函数之前,应该先了解一下堆栈的定义
基本类型的值也就存放在栈内存中:基本类型占有的内存大小是固定的,比如Boolean String Number undefind null,都是存在栈内存中
引用类型的值也就存放在堆内存中:引用类型的占有的内存大小不固定,比如Object Array 这些都是在堆内存中的
我们访问引用类型的时候,我们先是通过变量名去栈内存中拿到该值在堆内存中的地址,然后通过这个地址再去堆内存访问其值。
栈:存放基本类型(String,Number,Boolean,Null,Undefined),简单的数据段,占据固定大小的空间。
堆:大小不定也不会自动释放,存放引用类型(Function,Array,Object), 指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。
注:
我们创建的所有对象和数组 都是在堆内存中,
然后你调用数组或对象,其实是调用的数组或对象的地址,
这个地址就是新开辟的东西,放在栈里面的
当结束时候,这个栈内存,也就消失了。
1,创建函数的三种方式
1,函数声明 function name(){}
2,函数表达式 var name=function(){}
3,创建实例函数 var name=new function();
4,自执行函数
(function(){alert(1);})();
(function fn1(){alert(1);})();
ES6 中,箭头函数意义:箭头功能没有自己的this,不适合定义对象方法
函数的经历的历程由:单例模式()=>工厂模式()=>构造函数
2,单例模式
概念
:把描述同一个对象的属性和方法放在一个内存空间下,起到了分组的作用,这样不同事物之间的属性记时属性名相同,相互也不会发生冲突
// 单例模式下,我们把person1与person2叫做 命名空间
var jsPerson1={
name:"王小波",
age:48,
writeJs:function(){
console.log("my name is"+this.name+"i can write js~~");
}
};
jsPerson1.writeJs();
单例模式虽然解决了分组的作用,但是还不能实现批量的生产,属于手工作业模式 => 工厂模式产生了
3,工厂模式
1,概念
:把实现同一件事情的相同代码放到一个函数中,以后如果再想实现这个功能,不需要从新地编写这些代码,只需要执行当前的函数即可叫做函数的封装
2,代码封装优势:低耦合高内聚:减少页面中的冗余代码,提高代码的重复利用率
3,所有的编程语言都是面向对象开发的 => 类的继承、封装、多态
4,继承:子类继承父类中的属性和方法
function createJsPerson(name,age){
var obj={};// 工厂模式要在里面定义一个变量
obj.name=name;
obj.age=age;
obj.writeJs = function(){
console.log("my name is"+this.name+"i can write js~~");
};
return obj;
};
var p1 = createJsPerson("王小波",12);
工厂模式虽然解决了创建多个相似对象问题,但是没有解决对象识别问题,即不能知道一个对象的类型,所以构造函数产生了
4,构造函数
1,构造函数模式的目的:是为了创建一个自定义类,并且创建这个类的实例
2,构造函数的执行过程:
使用new关键字创建对象
在内存中创建一个空对象,把this指向刚创建的空对象
执行构造函数中的代码
默认返回新创建的对象;不管有没有return,或者return 后面是字符串后者数字等,都不起作用;但是如果 return 都面返回的是一个object,那么就会返回这个object值
3,构造函数的案例:
function CreateJsPerson(name,age) {
this.name = name;
this.age = age;
this.writeJs = function(){
console.log("my name is"+this.name+"i can write js~~");
};
}
let p2 = new CreateJsPerson('kevin2',8);
注:在构造函数模式中,new fn;执行时,如果fn不需要传递参数的话,后面的小括号可以省略
js中所有的类都是函数数据类型的,它通过 new 执行变成了类,但是他本身也是一个普通的函数
js中所有的实例都是对象数据类型的
4,判断数据的类型方法
- instanceof:判断某个对象是否是某个构造函数的实例
- typeof:检测数据类型作用,不能细分object下面的对象,数组,正则...
- attr in object:私有公有,只要有都显示true(检测某一个属性是否是属于这个对象)
- hasOwnProperty:用来检查属性是否为对象的私有属性
- hasPubProperty:用来检查属性是否为对象的公有属性
5,构造函数模式与工厂模式区别
- 执行时
普通函数执行:var p1=createJsPerson("王小波",12);// createJsPerson是一个函数名
构造函数模式:var p2 = new CreateJsPerson("王小波",12);// CreateJsPerson是一个类
注:而函数执行返回值(p2)就是CreateJsPerson这个类的一个实例
如果类是我们自己创建的,那么首字母要大写,这个是规范
- 在函数代码执行的时候
相同:都是形成一个私有的作用域,然后 形参赋值—>预解释—>代码从上到下执行,类和普通函数一样,他也有普通的一面
不同:
`工厂模式`:手动创建一个对象
`构造函数`:在代码执行之前,构造函数不用手动的创建对象,浏览器会默认创建一个对象数据类型的值(这个对象其实就是我们当前类的一个实例)
接下来代码从上到下执行,以当前的实例为执行的主体(this代表的就是当前的实例),然后分别把属性名和属性值赋值给当前的实例,浏览器会默认地把创建的实例返回
用 CreateJSPerson 这个类创建出来的实例,都拥有 writeJs 这个方法,但是不同实例之间的方法是不一样的
注:
1,浏览器在构造函数模式中,浏览器会默认的把我们的实例返回(返回的是一个对象数据类型的值);
2,如果我们自己手动写return返回:返回的是一个基本数据类型的值,当前返回实例是不变的,返回是的一个引用数据类型的值,当前的实例会被自己的值给替换掉
5,构造函数原型
console.log(p1.writeJs === p2.writeJs) // false
问题:当我们创建构造函数时,里面的方法通过创建会存储多份
- 每个构造函数都有一个属性 => 原型 (prototype)
- 通过原型添加方法,它的成员都可以访问到,此时存储的方法只有一个
console.log(p1.writeJs === p2.writeJs) // true
6,对象原型
5,原型链模式
1,实例识别:构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立开的
2,原型链模式:
1,通过
对象名.属性名
(键值对) 的方式获取属性值的时候,
2,首先在对象的私有属性上进行查找,
3,如果私有中存在这个属性,则获取的是私有的属性值;
4,当调用对象属性/方法时,先找对象私有属性/方法,如果没有,就调用原型中的属性/方法
5,如果元素上没有找到,则继续通过原型上__proto__
继续向上查找,一直找到Object.prototype
为止,没找到会报错
3,基于构造函数模式的原型模式解决了方法或者属性公有的问题;
把实例的相同的属性/方法,提取成公有的属性/方法,只要是它的实例对象,都可以获取
function CreateJsPerson(name,age){//自己创建的类,首字母要大写
this.name=name;
this.age=age;
};
CreateJsPerson.prototype.writeJs=function(){ // 公有的属性和方法
console.log("我是prototype的公有属性writeJs");
};
4,原型链接式
必须保证中间连接每一个返回的结果都是一个数组,才能使用链接式,不是数组,完成不了下面的链接,会报错;只有数组才能使用我们Array原型上定义的属性和方法
ary.sort(function(a,b){
return a-b;
}).reverse().pop();
5,批量设置原型上的公有属性和方法
- 起一个别名,把原来原型指向的地址赋值给我们的pro,现在他们操作的是同一个空间内存
- 重构原型对象的方式
只有浏览器天生给Fn.prototype开辟的堆内存里面才有constructor,而我们自己开辟的这个堆内存,没有这个属性,所有console.log(f.constructor) 指向就不是Fn而是object
function Fn(){
this.x=100;
};
Fn.prototype={//----这里给他直接创建一个新的堆内存空间
constructor:Fn// 这个添加指向,保持和原来一致
a:function(){},
b:function(){},
c:function(){},
d:function(){},
};
var f = new Fn;
f.a();
f.b();
console.log(f.constructor); // 没有处理之前,指向object,所以为了和原来保持一致,我们需要手动的增加constructor的指向
6,函数的三角关系(构造函数,对象,原型对象)
万物皆对象; 例外:Function.prototype是函数数据类型的值,但是相关操作和之前的一模一样;-----输出的结果是Empty/anonymous(空函数/匿名函数)
object是Function的儿子;Function的原型是object原型的儿子
1,函数的三角恋
- 对象:是通过构造函数创建的
- 对象的
__proto__
属性 = 对象原型 - 对象.prototyped.constructor = 该构造函数
- 构造函数.prototype = 该对象原型
image.png
所有原型对象的
proto都是Object原型对象,
proto的最巅峰值是 null,上图中,表示出来了
2,函数本身一些自己的属性
length
:0-----形参的个数
name
:“Fn”-----函数名
prototype
:类的原型, 在原型上定义都是当前Fn这个实例的公有方法
__proto__
:把函数当做一个普通的对象,指向Function这个类的原型
3,函数在整个js中,一个函数存在多面性
本身就是一个普通的函数
:执行的时候形成私有的作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁
类
:他有自己的实例,也有一个叫做prototype
属性是自己的原型,他的实例都可以指向自己的原型
普通类型
:和 var obj=Fn;
中的 obj
一样,就是一个普通的对象,他作为对象可以有一些自己的私有属性,也可以通过__proto__
找到Function.prototype
;当然这里如果this
指向是window
,没有return
,那么是undefined
;
7,函数中更改this的指向
这里是介绍三种方法,详情可见https://www.jianshu.com/p/c59b45c519f5
- apply()
- call()
- bind()
8,面向对象三大特征:封装,继承,多态(抽象)
1,封装:复制对象的成员给另一个对象
function extend(parent,child){
for(var key in parent){
if(child[key]){
continue;}
child[key]=parent[key]}
}
2,继承是类型和类型之间的关系
继承目的:把子类型中共同的成员提取在父类型中,代码重用
案例:
<!--都是人,都有name,age,sex这些属性,把这些属性提取出来作为公共的部分 -->
function Person(name,age,sex){
this.name=name
this.age=age
this.sex=sex;
}
<!--这里调用Person()构造函数,但是在Parent中,this指向与Child中的this指向不同,在这里要改变this指向,用call方法改变,不管是Parent与Child都可以用到公共属性 -->
function Parent(name,age,sex,salary){
this.name=name;
this.age=age;
this.sex=sex;
}
function Child(name,age,sex,salary){
Person.call(this,name,age,sex);
this.salary=salary;
}
组合继承:借用构造函数 + 原型继承
//通过原型让子类型继承父类型中的方法
Teacher.prototype = new Person();//Person是指一个构造函数
//因为这个是手动设置的,不是浏览器自动赋值的,所以这边constructor指向不是构造函数本身,这边要手动改变
Teacher.prototype.constructor=Teacher;
3,多态(这里我还没有整理,整理好,会自动更新)
9,函数的其他参数
函数本质上的对象,call,bind都是对象上的方法
- arguments: 是一个伪数组,获取到的是函数的实参
function fn(){
console.log(fn.arguments);// 2,3,4
}
fn(2,3,4)
- caller:函数的调用者
function fn(){
console.log(fn.caller) // test(因为是在test下面调用的)
}
function test(){
fn()
}
- name:函数的名称
function fn(){
console.log(fn.name) // fn
}
fn()
- length:函数的形参个数
注意这个不是实参个数
function fn(x, y){
console.log(fn.length) // 2
}
fn(3,7,9)
arguments 这里单独介绍一下
当函数参数个数不固定时候
在函数内部,我们可以通过arguments来获取实际传过来的参数
function fn(){
console.log(arguments);// 2,3,4
}
fn(2,3,4)
补充:
this 的指向:https://www.jianshu.com/p/bf8d22a67134
改变 this 的指向方法:https://www.jianshu.com/p/c59b45c519f5
网友评论