本篇笔记适合有一定有js面向对象基础的同学看。笔记的思路是由整体到局部,有浅层到深层!
我们知道,对象都有自己的属性和方法。所以用代码实现对象,就是分别实现它的属性和方法
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
console.log(this.name);
}
Person.prototype.getAge = function(){
console.log(this.age);
}
var p = new Person("geekWeb",20);
p.getName();
p.getAge();
从上面的代码中可以看出,如果我们的属性和方法不止一个,这种方式写出来代码的冗余性很强。可以用json的形式写出来
function Person(option){
this.name = option.name;
this.age = option.age;
}
Person.prototype = {
getName:function(){
console.log(this.name);
},
getAge:function(){
console.log(this.age);
}
}
var p = new Person({name:"geekWeb",age:20});
p.getName();
p.getAge();
对于json,这里有几点补充理解:
1. json协议:是数据传输通用协议。它是一种前端与服务器端的数据交换格式,它不是语言,只是一个规范,按照这种规范写法就能实现数据传递
2. json字符串:后台传递给前端的数据格式一般是json字符串,我们需要将其转换成json对象,再做其他处理
3. json对象与json字符串的相互转换:
* json对象——>json字符串:JSON.stringify(obj)
* json字符串——>json对象:JSON.parse(string)
4. json与xml:
* 相同点:都是一种通用协议;都可以用来描述数据
* 不同点:
JSON相对于XML来讲,数据的体积小,传递的速度更快些。
json占用带宽小,xml占用带宽大,
json没有xml这么通用
json可以和js对象互相转换,和js是天生的一对,因此广泛用于前端开发
小结:其实面向对象的语法规范并不是特别重要,重要的是面向对象的编程思维:对于一个新事物,要用面向对象的思维根据实际需求进行分析
构造函数:我们知道对象其实是使用函数实现的,并且对象本身也是一个函数。我们将用于创建对象的函数叫做构造函数。
1. 语法规范:
* 成员属性:this.name = …
* 成员方法:this.getName = function(){…}
2. 属性访问与修改:使用点语法
3. 属性遍历:for…in
4. 万物皆属性,万物皆变量
当前作用域内的变量、函数声明都会提升到作用域的最前面。
* 变量提升的只是声明,并没有赋值
* 对于函数,如果采用函数声明式语法function fn(){…},会提升整个函数体;而如果采用函数表达式语法var fn = function(){…},提升的只是变量的声明,函数体并没有声明。
var name = 'geekWeb';
function fn(){
console.log(name);
getName();
var name = "geekWeb1";
var getName = function(){
console.log('geekWeb2');
}
function getName(){
console.log('geekWeb3');
}
}
fn();
输出:undefined geekWeb3
解析:由于变量、函数提升,实际的代码执行为:
var name = 'geekWeb';
function fn(){
var name,getName;
function getName(){
console.log('geekWeb3');
}
console.log(name);
getName();
name = "geekWeb1";
getName = function(){
console.log('geekWeb2');
}
}
fn();
因为局部优先级大于全局,所以第一个参数打印结果为:undefined。
function Person(option){
// 设置公有属性
this.name = option.name;
this.age = option.age;
// 设置私有属性
var className = "私有属性";
// 设置公有方法
this.getAge = function(){
console.log(this.age);
}
// 设置私有方法
var publicFn = function(){
console.log(this.name); // 访问公有属性
console.log(className); // 访问私有属性,通过变量名直接访问
console.log(this.className); // undefined,className是私有属性
}
}
关于内存: 一切数据(数字、字符串、对象….)都是通过变量来管理,而定义变量的过程其实就是内存分配的过程,所以本质上:一切数据都是通过变量存放在内存中!
实例化的过程其实就是在内存中开辟新空间,拷贝构造函数属性并赋以实例的值的过程!
* 除了拷贝以外还会自动生成一个constructor属性,用于识别其是根据哪个构造函数创建的实例!
判断某个实例是不是根据某个构造函数创建的
var p = new Person();
if(p instanceof Person){
alert(true);
}
JavaScript中可以将数据类型分为:
* 数值型:Number
* 布尔型:Boolean
* 字符串:String
* 对象:Object
* 数组:Array
* 空值:Null
* 未定义:Undefined
var num = 1
var str = 'geekWeb'
var bool=false;
var arr=[];
var obj={name:'geekWeb'};
var date = new Date();
var fn = function(){}
console.log(typeof undefined)//'undefined'
console.log(typeof null) // 'object'
console.log(typeof true) //'boolean'
console.log(typeof 123) //'number'
console.log(typeof "abc") //'string'
console.log(typeof function() {}) //'function'
var arr=[];
console.log(typeof {}) //'object'
console.log(typeof arr)//'object'
console.log(typeof unknownVariable) //'undefined'
在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 “object”。
toString.call
console.log(toString.call(123)) //[object Number]
console.log(toString.call('123')) //[object String]
console.log(toString.call(undefined)) //[object Undefined]
console.log(toString.call(true)) //[object Boolean]
console.log(toString.call({})) //[object Object]
console.log(toString.call([])) //[object Array]
console.log(toString.call(function(){})) //[object Function]
console.log(Object.prototype.toString.call(str) === '[object String]') //-------> true;
console.log(Object.prototype.toString.call(num) === '[object Number]') //-------> true;
console.log(Object.prototype.toString.call(arr) === '[object Array]') //-------> true;
console.log(Object.prototype.toString.call(date) === '[object Date]') //-------> true;
console.log(Object.prototype.toString.call(fn) === '[object Function]') //-------> true;
console.log(arr instanceof Array) //---------------> true
console.log(date instanceof Date) //---------------> true
console.log(fn instanceof Function) //------------> true
// alert(f instanceof function) //------------> false
// 注意:instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。
console.log(arr.constructor === Array) //----------> true
console.log(date.constructor === Date) //-----------> true
console.log(fn.constructor === Function) //-------> true
为什么要引入原型对象:我们知道,每次实例化都需要分配内存存储这些数据,如果实例很多,那就要分配很多内存存储。而一般每个实例的属性不一样但行为是一样的,所以我们希望每次实例化的时候,只分配内存保存不一样的数据。而像方法,可以之分配一次空间,所有的实例共享这些方法,那就需要原型对象
原型对象:不管实例化多少次,都只会分配一次内存!它的本质是,原型对象中的属性和方法被所有的实例所共享。
结论: 将公有方法放在原型对象中,将不一样的属性放在构造函数中。
那么既然实例不拷贝原型中的属性方法,如何访问到其属性呢??通过原型链
先在自己的属性列表中寻找,如果找到则直接返回,如果找不到,则先找自身的一个隐藏属性proto,通过proto属性中保存的地址来链
如果构造函数和原型中有相同的属性
1. 根据属性搜索机制,原型中的属性会被屏蔽掉
2. 如果想要访问原型中的属性,有两种方法
1. delete:delete product.name
2. 使用全称:Product.prototype.name
该方法继承自 Object,判断一个属性是不是对象自身的属性
1. 用在普通对象上,可以判定一个属性是存在于构造对象的实例上还是原型对象上。
+ 如果是实例对象—ture
+ 如果是原型对象 – false
2. 用在json对象上,可以判断属性是不是该对象的直接属性
var iphone = {
name:'iphone',
age:100,
address:{home:'江苏',current:'北京昌平'}
}
console.log(iphone.hasOwnProperty('name')) //true
console.log(iphone.hasOwnProperty('age')) //true
console.log(iphone.hasOwnProperty('address')) //true
console.log(iphone.hasOwnProperty('home')) //false
有过后台语言基础的同学都知道,高级语言面向对象很多都有继承、多态、重写、覆盖的概念。虽然js本不是面向对象的语言,但是我们可以将js中一些面向对象的行为强行按照高级语言的那些概念上理解。
构造函数创建的实例为什么能访问原型对象的方法属性?
原型对象继承了构造函数对象,因而可以访问构造函数对象的一切方法,属性。
函数参数列表不同,包括个数、名称、顺序
对于构造函数中已经有的属性和方法,在原型对象中又定义了同样的属性、方法,可以理解成覆盖。
但是根据属性屏蔽理论,优先访问构造函数中的属性和方法。
想深入学习js,必须知道它的内存机制
学习内存分配之前,先要了解两种数据类型:
* 值类型:Number,String,Boolean,Undefined
* 引用类型:函数,数组,对象,Null,new Number()…
这两种数据类型在内存中的分配为:
* 栈内存:存放值类型和;存放引用类型的变量,变量中保存的是对象所在的地址
* 堆内存:存放对象真正保存的数据
可以总结值类型和引用类型的区别:
1. 内存分配机制不一样
* 值类型:创建一个变量,在栈内存中创建一个区域
* 引用类型:创建一个变量,在栈内存中保存的是变量真实入口的地址,在堆内存中保存的是对象真正保存的数据
2. 变量赋值是否的区别
* 值类型赋值,重新分配一个内存
* 引用类型赋值,只是复制了一个入口地址,指向的是同一块堆内存
用一个图形象描述:
小结:一切数据都是通过变量来管理,定义变量的过程其实就是分配内存的过程!所以,一切数据否是通过变量来存储的
从一个简单例子来理解js引用类型指针的工作方式
js分配的内存一般有三个生命周期:内存分配,内存使用,内存回收
js中的内存回收其实就是自动释放内存的过程。
1. 值类型自动释放内存:函数执行时,将值类型变量存放在栈内存中,函数执行完成,将值类型变量从栈内存中释放。闭包除外
2. 引用类型自动释放内存:通过引用计数来实现。每当实例化一次,引用计数就+1,当实例化被释放,引用计数-1。当引用计数为0时,内存完全释放,为空!
内存泄露是指一块被分配的内存既不能使用,又不能回收,这样泄露越多,最终导致占用内存越来越多,最终使得应用崩溃,甚至导致浏览器崩溃,最恶毒的就是导致系统也崩溃
创建对象的几种方式:
1. 字面量
2. Object
3. 内置对象
4. 构造函数
5. 原型对象
6. 拷贝模式
7. 工厂模式
8. 第三方
var o = new Object(); // 创建一个空对象,效果等同{}.
获取/设置对象的值:
很多时候,我们初始化一些对象时,它的属性值和方法都是一样的,我们希望执行函数时就直接生成一个对象。对于不一样的属性,通过参数的形式暴露出来。
function createPerson(name){
//1.原料
var obj = new Object();
//2.加工
obj.name = name;
obj.HP=100;
obj.MP=100;
obj.technologys=['普通攻击','横扫千军','地狱火','漫天飞雪'];
obj.attack = function(){
alert(obj.name+'发出攻击,导致对方10点伤害值')
};
obj.run = function(){
};
//3.出场
return obj;
}
var boy = createPerson('剑侠客');
boy.attack();
boy.run()
对象不需要new 直接运行函数即可(工厂加工一下)
核心:将一个json对象的所有属性拷贝给另一个对象
// target:目标对象
// source:原始对象
function extend(target,source) {
//遍历对象
for(var i in source){
target[i] = source[i];
}
return target;
}
使用:
var boy = {
name:'郭靖'
,image:'男性头像'
,age:20
,sex:'男'
};
var girl = {
name:'黄蓉'
,age:18
,image:'女性头像'
,sex:'女'
};
var zuixiake = extend({}, boy);
var huangrong = extend({},girl)
在js中万物皆对象。可以将对象分为三类:
1. BOM对象:windows,document,location,history,navigator,screen…
2. 内置对象:
3. 自定义对象
Browser Object Model 浏览器对象模型
任何我们自定义的全局变量,全局函数,全局对象等都会成为Windows对象的属性。
1. 全局常量:Infinity,NaN,undefined,null
* Infinity:表示js中能识别的最大值
* NaN:是Number类型,表示“不是数字”的数字。它不可以参与运算
* isNaN():判断某个数是不是NaN,即判断某个数是不是可以参与运算
2. 全局方法:eval(), isFinite(), isNaN(), parseFloat(), parseInt(),decodeURI(),decodeURIComponent(), encodeURI(), encodeURIComponent()
3. 弹出框:
* alert() 对话框中有一个确认按钮
* confirm() 对话框中有一个确认和取消按钮
* prompt() 对话框中有一个文本输入框,一个确认和取消按钮
4. 新窗口:open()打开一个新窗体; close()关闭窗体
5. 窗体控制
* moveBy(x,y) 从当前位置水平移动窗体x个像素,垂直移动窗体y个像素,x为负数,将向左移动窗体,y为负数,将向上移动窗体
* moveTo(x,y) 移动窗体左上角到相对于屏幕左上角的(x,y)点,当使用负数做为参数时会吧窗体移出屏幕的可视区域
* resizeBy(w,h) 相对窗体当前的大小,宽度调整w个像素,高度调整h个像素。如果参数为负值,将缩小窗体,反之扩大窗体
* resizeTo(w,h) 把窗体宽度调整为w个像素,高度调整为h个像素
6. 窗体滚动轴控制:
* scrollTo(x,y) 在窗体中如果有滚动条,将横向滚动条移动到相对于窗体宽度为x个像素的位置,将纵向滚动条移动到相对于窗体高度为y个像素的位置
* scrollBy(x,y) 如果有滚动条,将横向滚动条移动到相对于当前横向滚动条的x个像素的位置(就是向左移动x像素),将纵向滚动条移动到相对于当前纵向滚动条高度为y个像素的位置(就是向下移动y像素)
地址栏,一般包括url网址,协议,端口号,查询字符串
1. 属性:
* hash 设置或返回从井号 (#) 开始的 URL(锚)
* host 设置或返回主机名和当前 URL 的端口号
* hostname 设置或返回当前 URL 的主机名
* href 设置或返回完整的 URL
* pathname 设置或返回当前 URL 的路径部分
* port 设置或返回当前 URL 的端口号
* protocol 设置或返回当前 URL 的协议
* search 设置或返回从问号 (?) 开始的 URL(查询部分)
2. 方法:
* assign(),reload(),replace()…
DOM是我们学习的重点,涉及的知识点特别多。考虑到篇幅问题吗,不多做讲解。
* 我们编写的html内容会被编写成dom舒
* 操作dom树,其实就是对其增删改查
*
1. 普通属性:
js中每个函数都是一个Function对象。对象都是通过函数实现的
保存函数实参的组成的伪数组
伪数组:是一个包含length属性的json对象。特点:
* key是1,2,3…(起始不确定,由自己决定)
* 含有length属性,伪数组每次都要自己去计算length长度
* 不是数组,没有数组对象的方法
var json = {1:'',2:'',length:2}
arguments,通过document获取的数组都是伪数组,jQuery也是通过伪数组实现的 !
Array.prototype.slice.call(weiArr)
var fackArray1 = {0:'first',1:'second',length:2};
Array.prototype.slice.call(fackArray1);// ["first", "second"]
var fackArray2 = {length:2};
Array.prototype.slice.call(fackArray2);// [undefined, undefined]
callee是arguments 的一个属性成员,它表示对函数对象本身的引用,返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。常见用法:
1. 判断实参与形参个数是否一致
* argument.length:实参
* argument.callee.length:形参
2. 用于递归
// 传统方式
var fn=function(n){
if(n>0) return n+fn(n-1);
return 0;
}
// callee方式
var fn1=(function(n){
if(n>0) return n+arguments.callee(n-1);
return 0;
})(10);
返回函数的调用者
作用:
1. 借用:一个对象借用另一个对象的方法
2. 更改被借用方法的this指向,指向自身
function add(a, b) {
alert(a + b);
}
function sub(a, b) {
alert(a - b);
}
add.call(sub, 3, 1); // 4
用户名:"text" id="myText" value="input内容" />
<script>
var value = 'geekWeb'
function Fn1(){
console.log(this.value); // geekWeb
}
Fn1();
Fn1.call(document.getElementById('myText')); // input内容
script>
apply的功能用法与call一模一样。
它与call的区别:
* call传递是参数是平铺
* apply传递的参数是数组,使用的时候它会将数组拆分成很多个参数
使用apply借用的方法,参数必须是可变的
计算数组最大值:
function getMax(arr){
return Math.max.apply(null,arr);
/* return Math.max.call(null,1,2,3,4,5);*/
}
拼接两个数组
Array.prototype.push.apply(arr1,arr2);
可以使用apply实现继承构造函数中的属性方法,不能继承原型对象中的属性方法。
function Person(name,age){
this.name=name //名字
this.age=age //年龄
this.sayhello=function(){
console.log("人对象方法")
}
}
Person.prototype={
buy:function(){
console.log('测试是否能够继承原型中的方法')
}
}
//输出打印对象
function Print(){ //显示类的属性
this.funcName="我是打印对象"
this.show=function(){
console.log ('打印对象方法')
}
}
//学生对象
function Student(name,age,grade,school){ //学生类
Person.apply(this,arguments)
Print.apply(this,arguments)
this.grade=grade //年级
this.school=school //学校
}
当我们代码写错的时候,会定义一个对象用于保存出错的场景,各种信息等。使用try catch
try{
// 要检测的执行语句
}catch(e){
// 当出现错误时,执行的语句
}finally{
// 一定会执行的语句
}
属性:
* e.message:返回错误信息
* e.name:返回错误类型
在理解原型链之前我们先来了解两个概念:prototype,proto
* prototype:是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。
* proto是一个对象拥有的内置属性,是JS内部使用寻找原型链的属性。
注意:prototype是函数的内置属性,proto是对象的内置属性
new的过程拆分成以下三步:
var Person = function(){};
var p = new Person();
(1) 初始化一个空对象p,拷贝构造函数中的属性方法:var p={};
(2) p.proto = Person.prototype;
(3) 构造p对象:Person.call(p);
首先在构造函数中寻找属性方法,如果没有找到,就通过实例对象的隐藏属性proto寻找原型对象Person.prototype中属性方法,如果没有找到继续向上寻找,直至proto为null
所以:属性搜索机制的底层就是通过proto属性链接起来的
Object对象是Function对象的一个实例,所以Object的proto属性指向Function对象的原型
Object.__proto__ === Function.prototype
Object.prototype也是对象,也有proto属性,其为null
Object.prototype.__proto__ === null
Object的实例对象obj也是对象,它的proto属性为Object的原型对象:Object.prototype
所以,有:
obj.__proto__ === Object.prototype === Function.prototype.__proto__ === Object{...}
和Object对象一样,所有的内置对象都是Function对象的一个实例,所以他们的proto属性指向Function对象的原型
Number.__proto__ === Function.prototype // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype //true
Array.__proto__ === Function.prototype // true
RegExp.__proto__ === Function.prototype // true
Error.__proto__ === Function.prototype // true
Date.__proto__ === Function.prototype // true
和Object对象一致:内置对象对应的实例对象的proto属性指向它们构造函数的prototype属性
num.__proto__ === Number.prototype === Function.prototype.__proto__ === Number{...}
自定义对象是Function对象的实例对象,所以他的proto属性指向Function对象的原型
Person.__proto__ === Function.prototype
Person.prototype.__proto__ === Object.prototype
当前主流语言都是面向对象的语言,那么面向对象的特性是什么?封装,继承,多态。
将相同的属性,方法定义在父类中,将自身独特的属性,方法定义在子类。这样,子类既拥有父类的属性方法(共性),也拥有自身的属性方法(特性)
分两步:
第一步,构造函数继承: Person.call(this, a,b); 使用call或者apply都可以
第二步,原型继承:Student.prototype = new Person();
//基类 父类
var Person = function(){
this.name = '张三';
this.age = 20;
}
Person.prototype = {
say : function(){
console.log('Person.prototype - say');
}
}
var Student = function(){
Person.call(this, a,b);
//子类也可以拥有自己的一些属性和方法
this.sID='000001'
this.jiemu = '唱歌'
this.luobenTimes= 0
}
//原型继承
Student.prototype = new Person();
//子类也可以拥有自己的一些属性和方法
Student.prototype.getTeacher = function(){
console.log('王书奎');
}
将属性方法归到一个类中,以保证类的属性或方法不被外部看见,这就是对象的封装性。
面向对象程序运行时,相同的消息会送给多个不同类的对象,而系统可根据对象所属类,引发对应类的方法,而有不同的行为。
js不直接支持多态