JavaScript高级编程笔记(1)

1、按值传递和按引用传递

几乎所有的值传入函数都是按值传递的,但如果是传入对象的话,在函数体内修改对象属性的值的话就会改变。

即使在函数内部修改了参数的值,但原始的引用依然保持未变。在函数内部重写obj时,这个变量引用的就是一个局部对象了,这个局部对象会在函数执行完毕后立即被销毁

function setName(obj){
    obj.name = "Nicholas"; //只有这句的时候,alert输出的是 'Nicholas'

    //加了下面两句后   返回依然是'Nicholas' 
    obj = new Object();
    obj.name = "Greg"; 

}
var person = new Object();
setName(person);
alert(person.name);


2、检测类型

typeof 变量 //返回对象
typeof 操作符确定一个变量是字符串、数值、布尔值或者是undefined.
变量 instanceof 类型名
instanceof 操作符 如果是引用类型的话就会返回 true .检测不了基本类型,因为基本类型不是对象。


3、作用域链

延长作用域链:

  • with 语句
  • try-catch 语句的 catch

作用域中,函数体内var 的变量会提升到函数体最上面.

某环境中为了读取或写入引用一个标识符时,搜索从作用链前端开始向上逐级查询与给定名字匹配的标识符,如果在局部环境找不到标识符则继续沿作用链向上搜索。搜索过程将一直追溯到全局环境的变量对象。
下面是例子

var color = "blue";
function getColor(){
    return color;
}

alert(getColor()); //"blue"

4、垃圾收集

常用的垃圾收集方式是标记清除(mark-and-sweep)
还有引用计数(reference counting)
最好在不使用的时候手动断开JavaScript对象和DOM元素之间的链接。

object.element = null; //解除引用

垃圾收集器下次运行时就会删除这些值并回收它们占用的内存。

  • 基本类型值在内存中占据固定大小的控件,因此被保存在栈内存中。
  • 引用类型的值是对象,保存在堆内存中。

每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链。
当代码中存在循环引用对象时,“引用计数”算法就会导致问题。


5、引用类型

通过变量来访问属性

var m = 'abc'
k[m] = 4 // k.abc=4
Array类型

ECMAScript中的数组每一项可以保存任何类型的数据,大小可以动态调节。
检测数组用Array.isArray而不用instanceof是因为如果网页中包含多个框架,两个不同版本的Array构造函数会导致判断错误.
对象转换方法:

array.toString();
array.valueOf();
array.join(","); //将数组用逗号链接构成字符串

结合使用shift()和push()方法,可以像用队列一样使用数组。

array.push(); //入栈
array.pop(); //出栈
var m = array.shift(); //取得第一项,并使数组长度减一
array.unshift(value); //在数组前端添加任意项并返回新数组长度
排序:
array.reverse(); //反转排序
array.sort(); //默认情况下,按升序排序
array.sort(sortFunc); //sort可以接受一个比较函数作为参数进行排序
function compare(value1,value2){
  //如果第一个参数位于第二个之后则返回正数,相等则0,反之负数.
}
操作方法:
array.concat(); //基于当前数组的所有项创建当前数组的副本,并将接收到的参数添加到这个副本的末尾。
array.slice();//接受一到两个参数,即返回项的起始和结束位置。
位置方法
array.indexOf(value,index); //lastIndexOf则从后面开始寻找.
迭代方法
  • every():对数组中的每一项运行给定函数,如果该函数对 每一项 都返回true,则返回true.
  • filter():对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组。
  • foreach():对数组中的每一项运行给定函数,这个方法没有返回值。
  • map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
  • some():对数组中的每一项运行给定函数,如果该函数对 任一项 返回true,则返回true.
RegExp 类型

正则表达式

var expression = / pattern / flags;
string.match(pattern);  //只接受一个参数
  • g:表示全局模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止;
  • i:表示不区分大小写模式,即在确定匹配项时忽略模式与字符串的大小写;
  • m:表示多行模式,即在到达一行文本末尾时还会继续查找下一行中是否存在于模式匹配的项。

元字符若进入匹配字符串内必须转义:

( [ { \ ^ $ | ) ? * + . ] }

例子:

/*
 * 匹配所有.at,不区分大小写
 * /
var pattern4 = /\.at/gi;

6、Function类型

function sum(num1,num2){
    return num1+num2;
}
alert(sum(10,10)); //20

var anotherSum = sum;
alert(anotherSum(10,10)); //20

sum = null;
alert(anotherSum(10,10)); //20

使用不带圆括号的函数名是访问函数指针而非调用函数,此时anotherSumsum就都指向同一个函数,即使将sum设置为null,仍然可以正常调用。

不支持重载(重复定义只能覆盖)

函数的名字仅仅是一个包含指针的变量而已。

函数内部有两个特殊的对象:argumentsthis,
arguments 主要用途是保存函数参数,这个对象有一个名叫callee的属性,指向拥有这个arguments对象的函数.

  • caller是通过函数来调用,返回的是该函数的调用环境,也就是调用栈的最顶层(当最顶层为全局作用域时,返回null).
  • callee是通过arguments对象来调用,返回的是执行时的函数环境,通常用于匿名函数递归调用自身(匿名函数执行环境具有全局性,this指向windows).

例子主要用于松散耦合

prototype

ES5中prototype是不可枚举的。
每个函数都包含两个非继承而来的方法:apply(作用域,参数数组)call(作用域,逐个列举的参数)
apply和call主要作用是扩充函数运行的作用域 如this,window,o

引用类型和基本包装类型
  • 使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。
  • 基本包装类型只存在一行代码的执行瞬间,然后立即被摧毁.
var s1 = "some text";
s1.color = "red";
alert(s1.color);     //undefined

7、字符串操作

trim() 去空格

Locale用于本地化(少数语言会为Unicode大小写转换应用特殊的规则)
toUpperCase()toLocaleUpperCase() 大写
toLowerCase()toLocaleLowerCase() 小写

字符串模式匹配方法

var text = "cat,bat,sat,fat"
var pos = text.search(/at/); //search方法返回字符串中第一个匹配项的索引.如果没找到匹配项返回-1
//如果第一个参数是字符串,则只会替换第一个子字符串
var result = text.replace("at","ond");  // cond,bat,sat,fat
//如果第一个参数是正则表达式,且指定全局标志则全部替换
var result = text.replace(/at/g,"ond");  // cond,bond,sond,fond

8、单体内置对象

Global对象

eval()方法:只接受一个参数,即要执行的ECMAScript语句

属性类型

1、数据属性

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性 默认值为'true'
  • [[Enumerable]]:表示能否通过for-in循环返回属性,默认值为true
  • [[Writable]]:表示能否修改属性的值 默认值为true
  • [[Value]]:包含这个属性的数据值 默认值为undefined
    要修改属性默认的特性,必须使用ECMAScript5的Object.defineProperty(属性所在对象,属性名字,描述符对象)
var person = {};
Object.defineProperty(person, "name", {
           writable: false,
           value: "Nihao" m
 })
alert(person.name); //Nihao
person.name = "Greg";
alert(person.name) //Nihao

2、访问器属性(对象属性)

  • [[Configurable]]:表示能否通过delete删除属性从而重新定义属性 默认值为'true'
  • [[Enumerable]]:表示能否通过for-in循环返回属性,默认值为true
  • [[Get]]:在读取属性时调用的函数,默认值为undefined.
  • [[Set]]:在写入属性时调用的函数,默认值为undefined.
    要修改,必须使用ECMAScript5的Object.defineProperty(属性所在对象,属性名字,描述符对象)

3、读取属性的函数

Object.getOwnPropertyDescriptor(属性所在的对象,读取其描述符的属性名称);
alert(descriptor.value); //读取属性值

9、创建对象

1) 构造函数模式:

function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function(){
    alert(this.name);  
  };
}

var person1 = new Person('Greg',27,'Doctor');
console.log(person1.constructor == Person); //true
console.log(person1 instanceof Person); //true
console.log(person1 instanceof Object); //true

new操作符调用构造函数经历下面四个步骤:
(1)创建一个新对象
(2)将构造函数的作用于赋给新对象(因此this就指向这个新对象)
(3)执行构造函数中的代码(为这个新对象添加属性)
(4)返回新对象

2) 原型模式:

//通过这两个方法我们可以判断原型中是否有属性
function isProperty(object,property){
        return !object.hasOwnProperty(property) && (property in object);
    }

创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象.
使用原型的好处是可以让所有对象实例共享它所包含的属性方法
也就是说不必再构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中.

function Person(){}
person.prototype.name = "Nicholas";
person.prototype.sayName = function(){
  alert(this.name);  
};

var person1 = new Person();
person1.sayName();

与构造函数模式不同的是,新对象的这些属性方法是由所有实例共享的。

  • 创建一个新函数,就会有一组特定的规则为该函数创建一个prototype属性,指向函数的原型对象
  • 在默认情况下,所有原型对象都会自动获得一个constructor(构造函数属性),该属性指向prototype属性所在函数的指针.
  • 当调用构造函数创建一个新实例时,该实例的内部将包含一个指针指向构造函数的原型对象。(现在属性为proto),这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

关系图.jpg

在所有实现中都无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种对象。
如果[[Prototype]]指向调用isPrototypeOf()方法的对象(Person.prototype),则返回true.

alert(Person.prototype.isPrototypeOf(person1)); //true

Object.getPrototypeOf()方法返回[[Prototype]]的值.

alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"

代码读取某个对象的某个属性时,会执行一次搜索。搜索首先从对象实例本身开始,如果没有找到继续搜索指针指向的原型对象。
可以通过对象实例访问保存在原型的值,但却布恩那个通过对象实例重写原型中的值。如果在实例添加了一个和实例原型属性同名的属性,在实例中创建该属性只会屏蔽原型中的那个属性。(也就是说添加属性只会阻止访问原型对象中保存的同名属性,delete会让我们能够重新访问原型中的属性)。
使用hasOwnProperty()方法可以检测一个属性是存在于对象实例还是存在于原型。存在于对象实例时才会返回true.

in操作符
alert("name" in person1); //若name属性存在实例或存在原型中都返回true.

hasPrototypeProperty(person,"name") 若name属性存在于原型中 则返回true ,若在实例重写该属性后,则返回false.
Object.keys()接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
得到所有实例属性,可以使用Object.getOwnPropertyNames() (无论可否枚举)

调用构造函数时会给实例添加一个指向最初原型的[[Prototype]]指针,而将原型修改为另外一个对象就等于切断了构造函数最初原型之间的联系。(实例中的指针仅指向原型而不指向构造函数)

function Person(){
}
var friend = new Person();

Person.prototype = {  
  constructor: Person,
  sayName : function () {
    alert(this.name);  
  }
};
friend.sayName(); //error
TIM图片20181101160700.jpg

原型对象的问题:

  • 省略为构造函数传递初始化参数这一环节,导致所有实例在默认情况下将取得相同的属性值。
  • 对于包含引用类型值的属性来说问题比较突出。
function Person(){}
Person.prototype = {
  ...
  friends : ["Shelby", "Court"],
  ...
}

var person1 = new Person();
var person2 = new Person();
var person1.friends.push("van");

alert(person1.friends); //"Shelby", "Court","van"
alert(person2.friends); //"Shelby", "Court","van"
组合使用构造函数模式和原型模式
  • 构造函数模式用于定义实例属性
  • 原型模式用于定义方法和共享的属性
  • 组合使用这两个模式支持向构造函数传递参数。
function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["a", "b"];
}
Person.prototype = {
  constructor : Person,
  sayName : function(){
    alert(this.name);
  }
}

等效于

//动态原型模式
function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["a", "b"];
  if(typeof this.sayName != 'function'){
      Box.prototype.sayName = function(){
          alert(this.name);
      }
   } 
}

寄生构造函数模式

假设创建一个具有额外方法的特殊数组,

function SpecialArray(){
  //创建数组
  var values = new Array();
   //添加值
  values.push.apply(values, arguments);
  //添加方法
  values.toPipedString = function(){
      return this.join("|");
  };
  return values;
}
....
  • 说明情况:返回的对象与构造函数或者与构造函数的原型属性之间没有关系。所以不能用instanceof操作符来确定对象类型.
稳妥构造函数模式

稳妥对象(durable objects),指的是没有公共属性,且其方法也不引用this的对象.

按照该模式改写如下:

  function Person(name, age, job){
    //创建要返回的对象
    var o = new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.sayName = function(){
      alert(name);
    };
    //返回对象
    return o;  
  }

10、继承

回顾构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

原型链

function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
};
function SubType(){
  this.subproperty = false;
}

//继承了 SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){
  return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue());      //true
原型链.jpg

这个原型链其实少一环 就是所有的引用类型默认继承的Object .

  • 通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会导致原型链被重写。
  • 原型链有个问题,就是包含引用类型值的原型属性会被所有实例共享.
  • 原型链第二个问题是创建子类型的实例时,不能向超类型的构造函数中传递参数。
function SuperType(){
  this.colors = ["a","b","c"];
}

function SubType(){}
SubType.protype = new SuperType();

//省略定义过程
instance1.colors.push("d");
alert(instance2.colors); //a,b,c,d

借用构造函数

借用构造函数的基本思想是在子类型构造函数的内部调用超类型构造函数.因此通过使用apply()和call()方法在新创建的对象上执行构造函数.
在新创建的实例环境下调用构造函数,于是每个实例都会具有自己属性的副本了.

function SuperType(){
  this.colors = ["red","blue","green"];
}

function SubType(){
  //继承了 SuperType
  SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black

var instance2 = new SubType();
alert(instance2.colors); //red,blue,green
  • 相对于原型链而言,借用构造函数有一个优势,即可以在子类型构造函数中向超类型构造函数传递参数。
  • 借用构造函数的问题是无法避免构造函数模式存在的问题:方法都在构造函数中定义,函数复用无从谈起。

组合继承

组合继承(伪经典继承),指的是将原型链和借用构造函数的技术组合在一起,从而发挥二者之长的一种继承模式。背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

function SuperType(name){
  this.name = name;
  this.colors = ["red","blue","green"];
}

SuperType.prototype.sayName = function(){
  alert(this.name);
};

function SubType(name, age){
  //继承属性
  SuperType.call(this, name);
  this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
  alert(this.age);
};

var instance1 = new SubType("G",29);
instance1.colors.push("black");
alert(instance1.colors);      //"red,blue,green,black"
instance1.sayName();      //"Nicholas"
instance1.sayAge();      //29

var instance2 = new SubType("Greg",27);
alert(instance2.colors);      //"red,blue,green"
instance2.sayName();      //“Greg”
instance2.sayAge();      //27

原型式继承

借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
只想让一个对象于另一个对象保持相似的情况下,原型式继承是可以胜任的
ECMAScript5 通过新增Object.create()方法规范化了原型式继承.这个方法接受两个参数:用作新对象原型的对象和(可选)一个为新对象定义额外属性的对象

var person = {
      name: "abc",
      friends: ["a", "b", "c"]
};

var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});

console.log(anotherPerson.name); //"Greg"

寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似.即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

function createAnother(original){
    var clone = object(original);      //通过调用函数创建一个新对象
    clone.sayHi = function(){          //以某种方式来增强这个对象
        alert("hi");
    };
    return clone;
}

寄生组合式继承

组合继承是JavaScript最常用的继承模式,不过它的最大问题是无论在什么情况下都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
寄生组合式,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的思路是:不必为了指定子类型的原型而调用超类型的构造函数,所需要的无非就是超类型原型的一个副本而已。

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);    //创建对象
    prototype.constructor = subType;    //增强对象
    subType.prototype = prototype;      //指定对象
}
寄生组合式继承.jpg

小结:

ES OO编程创建对象的模式:
  • 工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象。(被构造函数模式所取代)
  • 构造函数模式:可以创建自定义引用类型,可以像创建内置对象实例一样使用new操作符。缺点是每个成员都无法得到复用,包括函数。
  • 原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。
  • JavaScript 继承主要通过原型链实现.原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。(问题是对象实例共享所有继承的方法和属性,解决这个问题的技术是借用构造函数,在子类型构造函数的内部调用超类型构造函数)使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。
    还存在下列可供选择的继承模式:
  • 原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制
  • 寄生性继承,与原型式继承非常相似,是基于某个对象或某个信息创建一个对象,然后增强对象,最后返回对象。
  • 寄生组合式继承,集优点于一身,是实现基于类型继承的最有效方式。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容