(1) 从视觉角度 : 对象是单个事物的抽象。
一本书、一个人都可以是对象,一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。
(2) 从编程角度 : 对象是无序键值对的集合,其属性可以包含基本值、对象或者函数等等
每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。
面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。
它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。
员工才面向过程,老大都是面向对象
面向对象与面向过程:
面向过程就是亲历亲为,事无巨细,面面俱到,步步紧跟,有条不紊,面向过程是解决问题的一种思维方式,关注点在于解决问题的过程。
面向对象就是找一个对象,指挥得结果,解决问题的思维方式,关注点在解决问题的对象上。
面向对象将执行者转变成指挥者
面向对象不是面向过程的替代,而是面向过程的封装
面向对象的特性:
- 封装性
- 将功能的具体实现,全部封装到对象的内部,外界使用对象时,只需要关注对象提供的方法如何使用,而不需要关心对象对象的内部具体实现,这就是封装。
- 继承性
- 在js中,继承的概念很简单,一个对象没有的一些属性和方法,另外一个对象有,
- 注意:在其他语言里面,继承是类与类之间的关系,在js中,是对象与对象之间的关系。
- [多态性]
- 多态是在强类型的语言中才有的。js是弱类型语言,所以JS不支持多态。
单独创建 ( 2种 ) 和 批量创建 ( 2种 )
缺点 : 不能很方便的批量创建
var person = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
缺点 : 属性要一个一个的添加,也是 不能很方便的批量创建,
//在js中,对象有动态特性,可以随时的给一个对象增加属性或者删除属性。
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
缺点 : 但却没有解决对象识别的问题,创建出来的对象都是Object类型的。
// 工厂函数
function createPerson (name, age) {
return {
name: name,
age: age,
sayName: function () {
console.log(this.name)
}
}
}
// 实例对象
var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)
function Person (name, age) {
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
}
var p1 = new Person('Jack', 18)
p1.sayName() // => Jack
var p2 = new Person('Mike', 23)
p2.sayName() // => Mike
注意点:
1. 构造函数 => 函数 + 首字母大写
2. 构造函数要配合new操作赋一起使用才有意义
3. new的作用 : (牢记)
- 创建一个新对象
- this指向了这个新对象
- 执行构造函数, 给对象添加属性和方法
- 返回新对象
4. 构造函数的作用 : 实例化对象 即:给对象赋值,添加属性和方法
使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = function () {
console.log('hello ' + this.name)
}
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => false
解决方案 :
// 全局声明的
function sayHello () {
console.log('hello ' + this.name)
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
// 先有的sayHello, 你再去指引的
this.sayHello = sayHello
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
缺点:会暴漏很多的函数,容易造成全局变量污染。
Javascript 规定,每一个构造函数都有一个 prototype
属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数( Person() )的实例 ( p、p1等 )继承 。 这个对象就是原型,也叫原型对象。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype
对象上。
原型的作用 : 共享数据 (也解决了构造函数资源浪费的问题)
// 原型:
//1. 在js中, 任何一个函数,都会自带一个属性 prototype, 这个属性指向了一个对象
//2. 这个prototype属性,也就是这个对象,我们叫做原型 ; (原型对象)
// 这个原型属性是一个对象
//3. 通过构造函数创建的对象,可以直接访问这个构造函数的原型中的所有内容(属性和方法)
//4. 最常用的就是 : 给构造函数的原型添加一个方法
代码 :
function Person (name, age) {
this.name = name
this.age = age
}
// dir 或者 log
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
console.log(this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayName === p2.sayName) // => true 解决了内容浪费的问题
这时所有实例的 type
属性和 sayName()
方法,其实都是同一个内存地址,指向 prototype
对象,因此就提高了运行效率。
构造函数 (Person):构造函数就是一个函数,配合new可以新建对象。
实例 (p1, p2):通过构造函数实例化出来的对象我们把它叫做构造函数的实例。一个构造函数可以有很多实例。
原型 (Person.prototype) :每一个构造函数都有一个属性prototype
,这个属性就叫做原型对象。通过构造函数创建出来的实例能够直接使用原型上的属性和方法。
构造+实例p,打印Person,有prototype属性,画图分配空间,newp,访问原型 p1亦是
注意:方法是给原型加,不是给构造函数加,方法是原型里的方法
实例对象p, 从Person.prototype里访问属性和方法,,这就叫继承 ; (拿过来用)
也就是说 :以后p想要找某个属性或者某个方法,,如果自己没有,,试着去看下原型里是否有
思考:内置对象中,有很多的方法,这些方法存在哪里?
var arr = [] , arr.push() Array.prototype;
var str = 'abc' str.indexOf() => String.prototype
__proto__
(★)实例对象p和原型对象的关系呢?
通过构造函数创建的对象,自带一个__proro__
属性,这个属性指向了构造函数的prototype属性,也就是原型对象。
获取原型对象:
通过构造函数.prototype
可以获取
通过实例.__proto__
可以获取(隐式原型)
它们指向了同一个对象构造函数.prototype === 实例.__proto__
又因为是浅白色,,这种是私有属性,,不可遍历的,,,,,不要用它来添加属性和方法,,只负责来检测它的原型即可
console.log(p.__proto__ === Person.prototype);
关系确立
1. 构造函数 是 妈妈,,妈妈只负责生孩子,,创建对象p
2. 原型对象是 爸爸,,,,实例对象能够继承原型对象的属性和方法
默认情况下,原型对象中值包含了一个属性:constructor,constructor属性指向了当前的构造函数。
构造函数实例化的对象 p , 能够访问原型对象里的全部属性和方法
如果是获取操作
沿着
__proto__
一直往上找
1. 会先在自身上查找,如果没有
2. 则根据__proto__对应的原型去找,如果没有
3. 一直找到Object.prototype,如果没有,那就找不到了。
如果是修改操作
//1. 只会修改对象自身的属性,
//2. 如果自身没有这个属性,那么就会添加这个属性,并不会修改原型中的属性。
任何一个对象,都有原型对象,原型对象本身又是一个对象**,所以原型对象也有自己的原型对象,这样一环扣一环就形成了一个链式结构,我们把这个链式结构称为:原型链。
__ proto __
//1. var s = new Strudent();
//2. var o = new Object();
//3. var arr = new Array();;
//4. var date = new Date();
//5. Math
总结:Object.prototype是原型链的尽头,Object.prototype的原型是null。
画图的本质 :
1. 通过打印 : p.__proto__ (原型对象) 来找
2. 找到 p.__proto__ (原型对象里的) constructor 的属性
3. 找到构造函数 其他的就解决了
//var date = new Date();
// date ==> date.__proto__(Date.prototype) ==> Object.prototype ==> null
浅色是不可遍历的
constructor: 返回对创建此对象的构造函数
hasOwnProperty:
isPrototypeOf:
propertyIsEnumerable:
toLocaleString:
toString:
valueOf:
hasOwnProperty()
方法会返回一个布尔值,判断某个属性是否是对象自己的属性。
// 参数 : 属性字符串
// 返回值 :true或者false
如果 是 自己的属性,会返回true。
如果 不是 自己的属性,会返回false。( 原型链上的属性也不算自己的 ),
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.gender = "男";
var p = new Person("zs", 18);
console.log(p.hasOwnProperty("name"));
console.log(p.hasOwnProperty("toString"));
console.log(p.hasOwnProperty("hasOwnProperty"));
console.log(p.hasOwnProperty("sex"));
console.log(p.hasOwnProperty("age"));
console.log(p.hasOwnProperty("gender"));
思考:hasOwnProperty 与 in 的区别? 以及两者的配合使用
in 操作符:如果属性 是自己的 + 原型上继承来的,==> true
// in 能判断的属性
//1. 自己的属性 OK
//2. 原型链上的属性也是
//1. in 的第一个用法
if ( 'name' in p ) {
// 'name' 是p自己的原型上的
}else {
// 不是自己的也不是原型上的
}
//2. in 的第二个用法 p.keys
for (var key in p) {
console.log(key); // 遍历自己的+原型加的
}
hasOwnProperty: 该属性必须是自己的属性,== > true,否则返回false。
// 在继承属性的情况下 , 打印自己的属性
function Person(name, age) {
this.nage = name;
this.age = age;
}
Person.prototype.gender = "男";
var p = new Person("zs", 20);
需求练习 : 通过遍历,查找自己的属性
for(var k in p) {
if(p.hasOwnProperty(k)) {
console.log(p[k]);
}
}
为什么要学习这个? 因为有些是不可枚举的,什么时候呢??自己添加的时候
propertyIsEnumerable() 方法返回一个布尔值,。 判断该属性是否是当前对象可枚举的自身属性
可枚举 ( 可以 for in 的 => 自身属性 + 原型链新加的属性)
自身属性
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.gender = "男";
var p = new Person("zs", 19);
for (var key in p) {
console.log(key); // 可枚举 name age gender
}
console.log(p.propertyIsEnumerable("name")) //true
console.log(p.propertyIsEnumerable("age")) //true
console.log(p.propertyIsEnumerable("gender")) //false
以前的 : p.gender = '女'; 这个是默认的
Object.defineProperty() 这个是更新详细的,细化的步骤
defineProperty的基本上使用
解决一些问题
//第一个参数:需要给哪个对象添加属性
//第二个参数:给对象添加的属性的名字
//第三个参数:给属性添加的修饰,是一个对象
var obj = {
name: "age",
age: 18}
// 底层 defineProperty 定义或者修改一个属性
Object.defineProperty(obj , 'sex', {
value: '女',
enumerable : true, //可遍历
writable : true, // 可修改
configurable : true //可删除或者修改其他特性
})
//1. 问题来了?? 无法遍历
console.log( obj.propertyIsEnumerable('sex'));
// 解决遍历问题
//1. enumerable : true 默认是 : false
//2. 问题来了? 无法改值
obj.sex = '男';
// 解决问题 :
//2. writable : true:, 默认是false
//3. 问题又来了??
// 不能删除或者修改其他特性(writeable除外)
// delete obj.sex;
// 底层 defineProperty 定义或者修改一个属性
// Object.defineProperty(obj , 'sex', {
// enumerable : false, //可遍历
// })
// console.log( obj.propertyIsEnumerable('sex'));
//3. 解决办法
//3. configurable : true
console.log(obj);
set和get
// 底层 defineProperty 定义或者修改一个属性
Object.defineProperty(obj, 'sex', {
// value: '女',
// writable : true, // 可修改
enumerable: true,
configurable: true, //可修改
set: function (newVal) {
sex = newVal;
},
get: function () {
return sex;
}
})
//注意 :
//1. value 和 writable要注释掉
这个和get和 set冲突
//2. 需要一个中介
//1.修改属性的值
obj.sex = '马哥';
console.log(obj);
// obj.sex = '伟哥';
//1. 打印可以
console.log(obj.sex);
//2. 遍历可以
for (var key in obj) {
console.log(key);
}
//3. 写在页面上可以
document.write(obj.sex);
toString() + toLocalString()
Object toLocaleString 返回调用 toString() 的结果。
基本上都是一样的结果: 比如两个数组
不一样的就是关于date的时候不一样
valueOf() + toString()
// toString() 和 valueOf() 的问题
// toString() 转化为字符串
// 作用 : 以字符串的形式表示
var arr = [1,4,7];
var date = new Date();
function fn() {
console.log('fn');
}
var obj = {
name :'zs'
}
console.log( arr.toString() );
console.log( date.toString() );
console.log( fn.toString() );
console.log( obj.toString() );
// valueOf
// 作用 : 返回object本身
console.log( arr.valueOf() );
console.log( date.valueOf() );
console.log( fn.valueOf() );
console.log( obj.valueOf() );
//区别呢???
var arr = [1,2,4];
alert( arr.valueOf() ); //1,2, 4
alert( arr.toString() ); // 1,2,4
// 验证上面
var arr = [1,2,3];
alert(Array.isArray(arr.valueOf())); // true
alert(Array.isArray(arr.toString())); //false
isPrototypeOf()
作用 : 用于测试一个原型对象是否存在对象的原型链上。
语法结构 :
A.isPrototypeOf(B):
//判断A 是否存在 B 的原型链上。 说白了就是判断A是否是B的祖先
演示 :
function Person() {
this.name = 'zs';
}
var p = new Person();
console.log( Person.prototype.isPrototypeOf(p));
console.log( Object.prototype.isPrototypeOf(p));
instanceof
作用 : 作用和isPrototypeOf类似,用于判断 构造函数 的prototype
属性是否在对象的原型链上。如果是,就返回true,如果不在,就返回false。
语法结构 :
object instanceof constructor
- 对象 instanceof 构造函数
- 演示 :
- ```js
console.log( p instanceof Person);
console.log( p instanceof Object);
返回值:检测构造函数的prototype属性是否在实例对象的原型链上。
区别 :
- A.isPrototypeOf(B) 判断A是否在B的原型链上 A: 是一个原型对象
//例如 : Person.prototype.isPrototypeOf(p)
- C instanceof D 判断D的prototype是否在A的原型链上 (D是C的构造函数) D:是一个构造函数
//例如 p instanceof Person
沙箱其实就是一个独立的环境,这个环境中任何的改变,都不会对外部环境产生影响。
函数自调用一样,在自调用函数内部的变量是不会影响到外部的,因此函数自调用模式也叫沙箱模式。
(function(window){
window.food = food;
})(window);
继承:子承父业
在js中的继承概念非常简单,拿来主义:一个对象自己没有的属性和方法,另一个对象有,拿过来使用,就实现了继承。
继承的目的:让一个对象可以使用另一个对象的属性和方法。
JS常见的几种继承模式:
把一个对象中的属性和方法拷贝到另一个对象中。
var wg = {
name :'玮哥',
jiCheng : function (obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
}
}
wg.jiCheng(mg);
wg.jiCheng(fg);
缺点 : 只能给一个对象继承, 而且给自身属性添加了新的方法
一个对象可以访问构造函数的原型中的属性和方法,那么如果想要让一个对象增加某些属性和方法,
只需要把这些属性和方法放到原型对象中即可。这样就实现了继承, 称之为原型链继承
直接给原型增加属性和方法
原型替换(注意:constructor)
代码 :
// Person.prototype.car = '法拉利';
// Person.prototype.price = 10000;
var xmg = {
constructor : Person,
car : '法拉利',
price : 1000
}
Person.prototype = xmg;
- 缺点 : 只能继承一个对象
混入继承的缺点 : 引入多的方法
原型继承的缺点 : 只能继承1个
// 原型和混入
//1. Person.prototype
//2. 混入 : for in
function Person() {
this.name = '马哥';
}
Person.prototype.extend = function (obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
}
// 想要继承很多个的 但是又不能新加法伤
var zs = {
car : '法拉利'
}
var ls = {
house : '海景房'
}
var ww = {
money : 10000
}
Person.prototype.extend(ls);
Person.prototype.extend(zs);
Person.prototype.extend(ww);
// p1
var p1 = new Person();
console.log(p1);
var p2 = new Person();
console.log(p2);
最初是由道格拉斯丶克罗克福德发布的一篇文章提出的,ECMAScript5新增了Object.create()方法来规范化了这种继承。
ES5中新增了一个方法Object.create()
,方法会使用指定的原型对象及其属性去创建一个新的对象。
//参数:proto 一个对象
//返回值:obj 新对象,新对象的原型就是proto
var obj = Object.create(proto);
console.log(obj);
var lw = {
car : '法拉利'
}
var o = Object.create(lw);
console.log(o);
构造函数.call (对象A)
翻译 : 构造函数里的this ,指向了 对象A
function Person() {
this.name = 'zs'
}
function Student() {
this.car = '法拉利'
}
var obj = {};
Person.call(obj);
Student.call(obj);
console.log(obj);
fn();//函数声明可以先调用,在声明
function fn(){
console.log("这是函数声明")
}
var fn = function() {
console.log("这是函数表达式");
}
fn();//函数表达式必须先声明,再调用
//函数也是对象,可以使用Function构造函数new出来
//相当于var fn = function(){}
var fn = new Function();
//语法:new Function(arg1,arg2,arg3..,body);
// 1. 所有的参数都是`字符串`类型。
// 2. 前面可以定义任意多个形参,`最后一个参数是代码体`。
var fn1 = new Function('num1');
var fn2 = new Function('num1','num2');
var fn3 = new Function('num1','num2','num3');
var fn3 = new Function('num1','num2','num3','alert(1)');
拓展1 : try...catch
// try : 尝试
// catch : 捕获
// 放一些可能出错的代码
// 可能出错的代码, 如果try里面出错了
// 会把错误的信息放到e里面., 执行catch,
// 如果try没有报错,,,catch不执行
var div = document.querySelector('div');
try {
div.onclick = function () {
console.log('哈哈');
}
} catch (e) {
console.log('错误了');
console.log('请写一个div');
}
console.log(1111);
拓展2 : eval函数--了解
eval的可以和new Function一样,执行字符串代码
注意:eval函数的功能非常的强大,但是实际使用的情况并不多。
eval形式的代码难以阅读
eval形式的代码无法打断点,因为本质还是还是一个字符串
在浏览器端执行任意的 JavaScript会带来潜在的安全风险,恶意的JavaScript代码可能会破坏应用
// 基本使用
eval('var num1 = 20; var num2 = 30; console.log(num1+num2)');
// 弊端 :
eval('while(3>2) { console.log("玩我吗") }');
拓展3 : 三种读取字符串代码块的方式 + 配合eval()
// 三种读取 字符串代码块 的方式 配合 eval
// 方式1 : 单双引号
var str = 'var num = 20; console.log(num)';
// 方式2 : '+
var str = 'if (3 > 2) {'+
'console.log(2);'+
'}';
// 方式3 : 反引号
var str = ` if (3 > 2) {
console.log(5);
}`;
根据函数内部this的指向不同,可以将函数的调用模式分成4种
函数调用模式
方法调用模式
构造函数调用模式
上下文调用模式(借用方法模式)
// 学前必备
函数:当一个函数不是一个对象的属性时,我们称之为函数。
方法:当一个函数被保存为对象的一个属性时,我们称之为方法。
// this的指向 :
// 谁调用this所在的函数,,this就指向谁
如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时this指向了window
function fn(){
console.log(this);//指向window
}
fn();
1.当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this被绑定到当前对象
var obj = {
name : 'zs',
sayHi:function(){
console.log(this);//在方法调用模式中,this指向调用当前方法的对象。
}
}
obj.sayHi();
2.事件中的this指向的是当前的元素,在事件触发的时候,浏览器让当前元素调用了function
var div = document.querySelector('div');
div.onclick = function () {
console.log(this);
}
如果函数是通过new关键字进行调用的,此时this被绑定到创建出来的新对象上。
function Person(){
console.log(this);
}
Person();//this指向什么?
var p = new Person();//this指向什么?
总结:谁调用 this所在的函数,,,this就指向谁
//分析思路:
// 1. 看this是哪个函数的 看这个函数是谁调用的,
// 2. this 指向了谁 ?
// 准备1
var age = 38;
var obj = {
age: 18,
}
// 准备2
var obj = {
name : 'zs'
}
console.log(obj.name);
console.log(obj['name']);
// 准备3
function fn() {
console.log('调用了');
console.log(this);
console.log(this.length);
}
var arr = [fn];
arr[0]();
// 相当于 arr.0() 如同 obj['name'] == obj.name 一样
// arr数组调用的
上下文调用模式也叫方法借用模式,分为apply与call
使用方法: 函数.call() 或者 函数.apply()
call方法
call方法可以调用一个函数,并且可以指定这个函数的this
指向
//1. 所有的函数都可以使用call进行调用
function fn() {
console.log('哈');
console.log(this);
}
fn();
// 1. 没有参数
fn.call();
// 2. 有参数的
// 语法 :
// call(参数1,参数2,参数3..)
// 参数1 : this 指向了第一个参数, (如果第一个参数为null,默认指向windw)
// 其余参数 : 就相当于函数的参数一样
function f() {
console.log(this);
console.log(arguments);
}
var obj = {};
f.call(obj,1,2); // 相当于 obj.call(1,2)
//说白了,call方法也可以和()一样,进行函数调用,call方法的第一个参数可以指定函数内部的this指向。
//一般情况下 : 应用场景
//继承 : 函数.call(对象)
//方法借用 : 方法.call(对象,参数,,,) ==> 对象.方法()
fn.call(thisArg, arg1, arg2, arg2);
例1 : fn.call([], 1, 2);
例2 :
var obj = {
name : '达达',
liaomei : function () {
console.log( this.name + '只会撩妹');
}
}
var mg = {
name : 'mg'
};
obj.liaomei.call(mg); // mg.liaomei();
借用对象的方法
伪数组与数组
伪数组也叫类数组
伪数组其实就是一个对象,但是跟数组一样,伪数组也会有length
属性,也有0,1,2,3
等属性。
arguments 的proto 和 var arr = [] 的 __ proto __ 比较
伪数组并没有数组的方法,不能使用push/pop
等方法
伪数组可以跟数组一样进行遍历,通过下标操作。
常见的伪数组:arguments
、document.getElementsByTagName的返回值
、jQuery对象
var obj = {
0:"张三",
1:"李四",
2:"王五",
length:3
}
//伪数组可以和数组一样进行遍历
伪数组借用数组的方法
Array.prototype.push.call(obj, "赵六");
var str = Array.prototype.join.call(obj, "-");
将伪数组转换成真数组
var arr = Array.prototype.slice.call(obj);
apply方法
apply()
方法的作用和 call()
方法类似,只有一个区别,就是apply()
方法接受的是一个包含多个参数的数组。而call()
方法接受的是若干个参数的列表
//apply和call一样,都可以调用函数,区别,参数不一样
//apply语法:
//参数1:指定this
//参数2:数组 ,需要把所有的形参都放到一个数组中
call和apply的使用场景:
如果参数比较少,使用call会更加简洁
如果参数存放在数组中,此时需要使用apply
函数.call(this的指向目标, 参数1,参数2) 参数列表
函数.apply(this的指向目标, [参数1,参数2]) 参数素组
bind方法 (了解)
bind()方法创建一个新的函数, 可以绑定新的函数的this
指向
//返回值:新的函数
//参数:新函数的this指向,当绑定了新函数的this指向后,无论使用何种调用模式,this都不会改变。
function fn() {
console.log(this);
}
var dd = {
name : 'zs'
}
// 绑定,返回一个新函数
var newFn = fn.bind(cc);
newFn();
// 即使再让别的对象调用,也不会改变
var obj = {
f : newFn
}
obj.f();
函数是由new Function创建出来的,因此函数也是一个对象,
所有的函数都是new Function的实例
。
画出下列代码的原型链结构
//var Person = new Function();
function Person(){
}
arguments:获取函数的实参,被函数内部的arguments替代了。 (废弃)
length:获取形参的长度
name:获取函数的名字,此属性不允许修改
caller:用于获取当前在函数是在哪个函数中调用的,已经被废弃了。(废弃)
constructor:指向当前构造函数,Function
call:调用函数,重新指向this
apply:调用函数,重新指向this
bind:重新指向this,返回一个新的函数,不调用。
总结:
所有函数都是new Function创建出来的,因此所有函数.__proto__
都是Function.prototype
所有对象都是new Object创建出来的,因此所有对象.__proto__
都是Object.prototype
预解析:预先解析
js执行代码分为两个过程: (记忆)
预解析过程(变量与函数提升)
代码一行一行执行
预解析过程:JavaScript解析器在执行代码前,会把所有变量的声明和函数的声明提升到当前作用域的顶部。例如var a = 11;
其实会分为var a;
和a = 11
两部分,其中var a;
会被提升。
预解析规则:
把var 声明的变量提升,,赋值不提升;
包括 : var num = 20; var fn = function() {...}
函数声明(不是表达式), 整体提升
包括 :
function test() {
}
函数同名,后者覆盖前者
函数与变量同名,,函数覆盖变量
推荐:不要在一个作用域内重复的声明相同的变量和函数
作用域:变量起作用的区域,作用域决定了一个变量被定义在哪里,以及该如何被查找。
全局作用域 : 函数外部的区域;
函数作用域 : 函数内部的区域;
全局变量:在函数外定义的变量就叫全局变量,全局变量在任何地方都能访问到。
局部变量:在函数内定义的变量就叫局部变量,局部变量只有在当前函数内才能访问到。
隐式全局变量 : 只赋值未声明的变量, 隐式全局也是全局;
var num = 11;//全局变量
function fn(){
var num1 = 22;//局部变量
console.log(num1);
num3 = 44;
}
console.log(num);
编程语言中,作用域规则分为两种:
词法作用域(静态作用域)
动态作用域
JavaScript采用的是词法作用域
规则,词法作用域
也叫静态作用域
,变量在函数声明的时候,它的作用域就定下来了,与函数的调用无关。
var num = 123;
function f1() {
console.log(num);
}
function f2(){
var num = 456;
f1();
}
f2();//打印啥?
作用域链:只要是函数,就会形成一个作用域,如果这个函数被嵌套在其他函数中,那么外部函数也有自己的作用域,这个一直往上到全局环境,就形成了一个条作用域链。
变量的搜索原则
:
从当前作用域开始搜索变量,如果存在,那么就直接返回这个变量的值。
如果不存在,就会往上一层作用域查询,如果存在,就返回。
如果不存在,一直查询到全局作用域,如果存在,就返回。如果不存在说明该变量是不存在的。
如果一个变量不存在
获取这个变量的值会报错xxx is not defined;
,
给这个变量设置值,那么设置变量就是隐式全局变量
。
闭包(closure)
是JavaScript语言的一个难点,也是JavaScript的一个特色,很多高级的应用都要依靠闭包来实现。
// 1.里面可以访问外面的变量
var num = 10;
function outer() {
console.log(num);
}
outer();
//2. 外面想访问里面的变量怎么办?
function outer() {
var num = 10;
return num; // num + 20;
}
var res = outer();
console.log(res);
//3. 外面想操作修改这个变量怎么办??
// 上面的例子只是返回,有多少返回多少,就算你在这里运算也是
// 返回多少就是多少,也是死的
function outer() {
var num = 10;
function inner(n) {
num = n;
console.log(num);
}
// 这里依然是内部操作者的
inner(50)
}
outer();
//4. 终极版 : 外面想操作函数内部的数据
function outer() {
var num = 10;
function inner(n) {
num = n;
console.log(num);
}
return inner;
}
var f = outer();
f(60);
百度百科 : 闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
维基百科 : 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数
总结 : 在JavaScript中,在外部函数中嵌套另一个子函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。
闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用
// 当内部函数访问了外部函数的变量的时候,就会形成闭包。
// scope 作用域
// 断点调试
function outer() {
var num = 10;
function inner() {
console.log(num); // 产生
console.log(11); // 不产生
}
return inner;
}
outer()();
需求:统计一个函数的调用次数
var count = 0;
function fn(){
count++;
console.log("我被调用了,调用次数是"+count);
}
fn();
fn();
fn();
缺点:count是全局变量,不安全。
使用闭包解决这个问题!!!!
function outer(){
var count = 0;
function add(){
count++;
console.log("当前count"+count);
}
return add;
}
var result = outer();
result();
result();
result();
把变量保护起来 ()
这些变量的值始终保持在内存中 ==> 应用
使用闭包实现私有变量的读取和设置
function outer(){
var num = 10;
function set_num(n){
num = n;
}
function get_num(){
return num;
}
return {
set_num:set_num,
get_num:get_num
}
}
var obj = outer();
obj.set_num(2000);
console.log(obj.get_num());
闭包占用的内存是不会被释放的,因此,如果滥用闭包,会造成内存泄漏的问题。闭包很强大,但是只有在必须使用闭包的时候才使用。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management
内存:计算机中所有程序的运行都是在内存
中进行的,因此内存的性能对计算机的影响非常大,运行程序需要消耗内存,当程序结束时,内存会得到释放。
javascript分配内存:当我们定义变量,javascript需要分配内存存储数据。无论是值类型或者是引用类型,都需要存储在内存中。
垃圾回收:当代码执行结束,分配的内存已经不需要了,这时候需要将内存进行回收,在javascript语言中,垃圾回收机器
会帮我们回收不再需要使用
的内存。
引用记数法清除
引用记数垃圾收集:如果没有引用指向某个对象(或者是函数作用域),那么这个对象或者函数作用域就会被垃圾回收机制回收。
var o = {
name:"zs"
}
//对象被o变量引用 ,引用记数1
var obj = o; //变量被o和obj引用,引用记数2
o = 1; //o不在引用对象了, 引用记数1
obj = null; //obj不在引用对象了,引用记数0,可以被垃圾回收了。
标记清除法清除
使用引用计数法进行垃圾回收的时候,会出现循环引用导致内存泄漏的问题。因此现代的浏览器都采用标记清除法来进行垃圾回收。
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象Window)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。
function outer(){
var count = 0;
function fn(){
count++;
console.log("执行次数"+count);
}
return fn;
}
var result = outer();
result();
result = null;//当函数fn没有被变量引用了,那么函数fn就会被回收,函数fn一旦被回收,那么outer调用形成的作用域也就得到了释放。
正则表达式:用于匹配规律规则的表达式,正则表达式最初是科学家对人类神经系统的工作原理的早期研究,现在在编程语言中有广泛的应用,经常用于表单校验,高级搜索等。
学习要求 : 能识别各种正则表达式; 会使用编写简单的正则表达式
构造函数的方式
var regExp = new RegExp(/\d/);
正则字面量
var regExp = /\d/;
正则的使用
// test 是正则的其中一个常用的方法
// 结构 : 正则.test('要检查的字符串')
// 返回值 : true/false
/\d/.test("aaa1");
正则表达式由一些普通字符和元字符组成,普通字符包括大小写字母、数字等,而元字符则具有特殊的含义。
特殊字符换 http://www.w3school.com.cn/js/js_special_characters.asp
| 表示或,优先级最低
()
优先级最高,表示分组
// 优先级 | ()
// |
console.log(/foot|boot/.test('bootfoot'));
console.log(/foot|boot/.test('bootf'));
console.log(/foot|boot/.test('bfoot'));
// ()
console.log(/(f|b)oot/.test('bootfoot'));
console.log(/(f|b)oot/.test('bootf'));
console.log(/(f|b)oot/.test('bfoot'));
console.log(/(f|b)oot/.test('bfot'));
[]
在正则表达式中表示一个字符的位置,[]里面写这个位置可以出现的字符。
// 字符里包含a | b | c => 都为true
console.log(/[abc]/.test('a123'));
[^]
在中扩号中的^表示 非,除了 的意思。
判断 : 是否包括除了XX以外的字符串,如果包含了除了XX以外的字符都可以为true
//[^0]:除了0以外的字符
console.log(/[^0]/.test("1aaa000"));
console.log(/[^0]/.test("a"));
[a-z]
[1-9]
表示范围
console.log(/[a-z]/.test("d"));//小写字母
console.log(/[A-Z]/.test("d"));//大写字母
console.log(/[0-9]/.test("8"));//数字
console.log(/[a-zA-Z0-9]/);//所有的小写字母和大写字母以及数字
我们前面学习的学习的正则只要有满足的条件的就会返回true,并不能做到精确的匹配。
^表示开头 []里面的^表示取反
$表示结尾
console.log(/^chuan/.test("dachuan"));//必须以chuan开头
console.log(/chuan$/.test("chuang")); //必须以chuan结尾
console.log(/^chuan$/.test("chuan")); //精确匹配chuan
//精确匹配chuan,表示必须是这个
console.log(/^chuan$/.test("chuanchuan"));//fasle
注意 : 量词用来控制出现的次数,一般来说 量词 和 边界 一起使用
切记 : 量词 和 精确匹配 一起使用
//量词用来控制出现次数的
*
表示能够出现0次或者更多次,x>=0;
console.log(/a*/.test('123'));
+
表示能够出现1次或者多次,x>=1
console.log(/^a+$/.test('aaaa'));
?
表示能够出现0次或者1次,x=0或者x=1
var str="1, 100 or 1000?";
var pat=/10?/g;
console.log(str.match(pat));
{n}
表示能够出现n次
{n,}
表示能够出现n次或者n次以上
{n,m}
表示能够出现n-m次
思考:如何使用{}来表示*+?
学习目的
验证座机
比如010-12345678 0797-1234567
开头是3-4位,首位必须是0
-后面是7-8位
var phoneReg = /^0\d{2,3}-\d{7,8}$/;
验证姓名
只能是汉字 \u4e00-\u9fa5
长度2-6位之间
汉字范围[\u4e00-\u9fa5]
var nameReg = /^[\u4e00-\u9fa5]{2,6}$/;
验证QQ
只能是数字
开头不能是0
长度为5-11位
var qqReg = /^[1-9]\d{4,10}$/;
验证手机
11位数字组成
号段13[0-9] 147 15[0-9] 17[0178] 18[0-9]
var mobileReg = /^(13[0-9]|147|15[0-9]|17[0178]|18[0-9])\d{8}$/;
验证邮箱
[email protected]
[email protected]
前面是字母或者数字
必须有@
@后面是字母或者数字
必须有.
.后面是字母或者数字
var emailReg = /^\w+@\w+(\.\w+)+$/;
var str = " 123AD asadf asadfasf adf ";
1 替换掉字符串中的所有空白
2. 将所有的ad替换成xx i:忽略大小写
3. 将所有的ad/AD替换成xx
var str = 'abc,efg,123,abc,123,a'
4. 所有的逗号替换成叹号
var jsonStr = '[{"name":"张三",score:80},{"name":"张三",score:90},{"name":"张三",score:81}]';
5. 把所有成绩都修改成100分
var str = jsonStr.replace(/\d+/g,'100');
//1.
var str = "我的手机号是:18511241111, 我的女朋友的手机号是:13211111111,我的前女友的手机号是:18522223333,我的前前女友的手机号是:18511112293";
//需求:把字符串中所有的手机号找出来。
var arr = str.match(/(13[0-9]|147|15[0-9]|17[0178]|18[0-9])\d{8}/g);
//结果 : ["18511241111", "13211111111", "18522223333", "18511112293"]
//2. 回顾 ?
var str = '1,10,100,1000';
var arr = str.match(/10?/g);
console.log(arr);
//今天是2018-05-11, 要求;提取得到年月日;
var str = "今天是2018-05-11, 要求;得到年月日"
// 提取
// var reg = /(\d{4})-(\d{2})-(\d{2})/;
var reg = /\d{4}-\d{2}-\d{2}/;
//正则表达式只有:2个: test:测试 exec:提取
reg.test()
reg.exec()
//字符串有两个方法: replace match: 支持参数传正则
str.replace()
str.match();
//正则的使用:
//1. 字符串的replace: 正则的替换
//2. 字符串的匹配:match: 匹配某个字符串中所有符合规律的字符串。
//3. 正则的测试:test: 表单校验,判断某个字符串是否符合正则的规律
//4. 正则的提取: 提取匹配的字符串的每一个部分。 ()进行分组