你不知道的javascript

时间:2022-09-07 11:39:30

一、对象

JavaScript简单类型有数字、字符串、布尔值、null、undefined,其他所有的值都是对象(数组、函数、正则表达式都是对象)。

数字、字符串、布尔值虽然拥有方法(包装对象),但并不是对象。

包装对象:

每当读取一个基本类型值的时候,后台会创建一个对象的基本包装类型的对象,从而能够调用一些方法来操作这些数据。

var s1 = 'abcdefg' ;
var s2 = s1.substring(2) ;

后台自动完成下列处理:

  1. 创建String类型的一个实例
  2. 在实例上调用指定的方法
  3. 销毁这个实例

所以上面代码等同于:

对象字面量

var flight = {
airline: "Oceanic",
number: 815,
departure: {
IATAL: "SYD",
time: "2004-09-22 14:55",
city: "Sydney"
},
arrival: {
IATA: "LAX",
time: "2004-09-23 10:42",
city: "Los Angeles"
}
}

检索

  • [] : flight['number']
  • . : flight.number

更新

通过赋值语句更新,如果属性名已经存在于对象中,则被替换;如果对象中没有那个属性名,则添加。

stooge['first-name'] = 'Jerome'

引用

对象赋值通过引用来传递,它们永远不会被拷贝。

var a = {
name: 'a'
}
var b = a
b.name = 'b'
console.log(a.name) // b

这里牵扯出 JavaScript 深拷贝和浅拷贝的问题
上例是浅拷贝使用Object.create可以进行深拷贝

var a = {
name: 'a'
}
var b = Object.create(a)
b.name = 'b'
console.log(a.name) // a

自定义方法深拷贝见下:

var deepCopy= function(source) {
var result={};
for (var key in source) {
result[key] = typeof source[key]===’object’? deepCoyp(source[key]): source[key];
}
return result;
}

此时 var b = deepCopy(a) 得到的 b 就和 a 没有引用关系,即修改 b 不会影响 a

原型

每个对象都连接到一个原型对象,并且从中继承属性。所有通过对象字面量创建的对象都连接到 Object.prototype 这个JavaScript中标准的对象。

创建一个对象时,可以选择某个对象作为它的原型:

var o = {o1:1,o2:function(){alert(1)}}
function F(){}
F.prototype = o
var f = new F()

反射

使用 hasOwnProperty 检查属性是否是对象独有的,它并不会检查原型链。

flight.hasOwnProperty('number');    //true

枚举

for in 可以遍历对象中所有的属性名(见深拷贝部分)

删除

delete 可以删除对象属性,不会触及原型链中的任何对象

减少全局变量污染

最小化使用全局变量的一个方法是创建唯一一个全局变量:

var App = {}
App.stooge = {
"first-name": "Joe",
"last-name": "Howard"
}
App.flight = {
airline: "Oceanic",
number: 815
}

减少全局变量污染另一个办法是使用闭包进行信息隐藏

二、函数

函数包含一组语句,是Javascript的基础模块单元,用于代码复用、信息隐藏和组合调用。

一般来说,所谓编程就是将一组需求分解成一组函数与数据结构的技能。

函数对象

函数就是对象,对象是键值对的集合并且拥有一个连到原型对象的隐藏连接。对象字面量产生的对象连接到 Object.prototype ,函数对象连接到 Function.prototype (该原型对象本身又连接到 Object.prototype)。

var add = function(a,b){
return a + b;
}

函数表达式包含四部分:

  1. 保留字 function
  2. 函数名,可以被省略(匿名函数)
  3. 参数,逗号分隔
  4. 花括号中的语句

函数表达式允许出现在任何允许表达式出现的地方,函数也可以被定义在其他函数中。一个内部函数可以访问自己的参数和变量,同时它也能方便地访问它被嵌套在其中的那个函数的参数和变量。通过函数表达式创建的函数对象包含一个连到外部上下文的连接,被称为闭包。

调用

函数在调用的时候有两个附加参数:thisarguments

this 是调用上下文,值取决于函数调用的模式。

1.方法调用模式

一个函数被保存为对象的一个属性时,即为一个方法。当一个方法被调用时,this 被绑定到该对象。

每个函数在创建时附有两个附加的隐藏属性:

  • 函数上下文
  • 实现函数行为的代码

每个函数对象在创建时也会带一个 prototype 属性,它的值是一个拥有 constructor 属性且值为该函数的对象。

函数表达式

函数对象可以通过函数表达式来创建:

var dog = {
name : 'xxx' ,
leg:{
sum : 4 ,
move:function(){
console.log(this) ; //Object,是leg对象,而不是dog对象,下面证明了
alert(this.name) ; //underfined
alert(this.sum) ; //
}
}
}
dog.leg.move();

2.函数调用模式

函数仅仅当做函数来调用时,this 被绑定到全局对象。

var a = 111 ;
function t1(){
var a = 1
function t2(){
console.log(this.a) //111,这其实很不合理,应该指向t2的。
}
t2()
}
t1()

这其实是语言设计上的一个错误,倘若语言设计正确,当内部函数被调用时,this 应该仍然绑定到外部函数的 this 变量。

3.构造器调用模式

如果一个函数前面带上 new 调用,那么将创建一个隐藏连接到该函数的 prototype 成员的新对象,同时 this 将被绑定到那个新对象上。

function Dog(name){
this.name = name ;
}
Dog.prototype.cry = function(){
alert(this.name)
}
var dog1 = new Dog('xxx');
dog1.cry(); // 'xxx'

4.Apply/Call调用模式

apply 接受两个参数,第一个是将被绑定给this的值,第二个就是一个参数数组。

call 与 apply 相同,不过第二个参数不是数组。

var dog = {
leg : 4 ,
color:'yellow'
}
var color = 'red' ;
function t(){
alert(this.color) ;
}
t(); // red , 因为指向this在函数中调用指向window
t.call(dog); //yellow , 把t()的作用域指向了dog

再来说说 arguments,它是一个类数组对象(拥有length属性,但缺少所有数组方法)。通过它可以访问函数调用时传递给函数的参数列表。

返回

一个函数调用时,将暂停当前函数的执行,传递控制权和参数给新函数。它从第一个语句开始执行,并在遇到关闭函数体的 } 时结束。使得函数把控制权交还给调用该函数的程序部分。

return 语句可用来使函数提前返回,当 return 执行时,函数立即返回而不再执行余下的语句。一个函数总是会返回一个值,如果没有指定返回值,则返回 undefined 。

如果函数前加上 new 来调用,且返回值不是一个对象,则返回this(该新对象)。

异常

抛出异常

function add(a,b){
if(typeof a!=='number' || typeof b!=='number'){
throw {
name: 'TypeError',
message: 'add needs numbers'
}
}
return a + b;
}

throw 语句中断函数的执行,抛出一个 exception 对象,该对象包含可识别异常类型的 name 属性和一个描述性的 message 属性。

该 exception 对象将被传递到一个 try 语句的 catch 从句

try{
add('seven')
}catch(e){
console.log(e.name)
console.log(e.message)
}

如果在 try 代码块中抛出异常,控制权就跳转到其 catch 从句。

给类型增加方法

Number.prototype.integer = function(){
return Math[this < 0 ? 'ceiling' : 'floor'](this) //this指向实例
} var num = 10/3
console.log(num.integer()) ; //

作用域

作用域空间那个值变量和参数的可见性和生命周期,对程序员来说很重要,因为它减少了命名冲突,并且提供了自动内存管理。

JavaScript没有块级作用域,却有函数作用域:定义在函数中的参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中任何地方都可见。

下面这个例子与以对象字面量初始化对象不同,通过调用一个函数形式去初始化对象,返回一个对象字面量。此函数定义了一个 val 变量,该变量对 addVal 和 getVal 总是可用的,但函数的作用域使得其对其他程序来说是不可见的。

// 从设计模式的角度来说这是模块模式
var o = (function(){
var val = 0;
return {
addVal: function(){
val += 1
},
getVal: function(){
console.log(val)
}
}
})()

联想到之前我做的一个小游戏,是20秒内完成任务,使用 restTime 做倒计时变量。后来同事把restTime修改了,成绩贼高。最后我就是用这种办法把 restTime像 val 一样隐藏了起来。

闭包

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。

var o = function(){
var val = 0;
return {
addVal: function(){
val += 1
},
getVal: function(){
console.log(val)
}
}
}
var oo = o()
oo.addVal()
oo.addVal()
oo.getVal() //

当调用 o 时,返回一个包含addValgetVal的新对象,该对象的引用保存在 oo 中。虽然 o 返回了,但是 oo 的addValgetVal有访问 val 的特权,它们可以访问被创建时所处的上下文,这就是闭包。

模块

可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。

具体见『作用域』部分-模块模式

三、继承

当一个函数对象被创建时,Function 构造器产生的函数对象会运行类似这样的代码:

this.prototype = {constructor: this}

每个函数都会得到一个 prototype 对象,其值是包含一个 constructor 属性且属性值为该新函数对象。该 prototype 对象是存放继承特征的地方。

四、数组

区分数组和对象

一个常见的错误是在须使用数组时使用了对象,在须使用对象时使用了数组。其实规则很简单:当属性名是小而连续的整数时,用数组,否则就用对象。

JavaScript中数组 typeof 返回是 object,这并不能区分数组和对象。

var is_array = function(value){
return value &&
typeof value === 'object' &&
typeof value.length === 'number' &&
typeof value.splice === 'function' &&
!(value.propertyIsEnumerable('length'))
}
  • 首先,我们判断这个值是否为真,我们不接受 null 和其他为假的值。
  • 其次,我们判断这个值的 typeof 运算结果是否是 object 。对于对象、数组和null来说,将得到 true
  • 第三,我们判断这个值是否有一个值为数字的 length 属性,对于数组是 true ,对于对象则为 false
  • 第四,判断这个值是否包含一个 splice 方法。对于数组,返回 true
  • 最后,我们判断 length 属性是否是可枚举的

这真的很复杂,实际上,我一直是这样用的,什么类型都能检测,堪称万能:

var toString = Object.prototype.toString
function isObject(obj) {
return toString.call(obj) === "[object Object]"
}
function isString(obj) {
return toString.call(obj) === "[object String]"
}
function isArray(obj) {
return toString.call(obj) === "[object Array]"
}
function isFunction(obj) {
return toString.call(obj) === "[object Function]"
}

五、方法

Array

concat(item...)

返回一个新数组,并不会修改原数组

var a1 = [2,3]
var a2 = [332,12]
console.log( a1.concat(a2) ); //[ 2, 3, 332, 12 ]

join(separator)

把一个 array 构造成一个字符串,并用 separator 作为分隔符把它们连接在一起。

pop(item...)

移除数组中最后一个元素并返回该元素

push(item...)

将一个或多个元素添加到数组尾部,会修改原数组

reverse()

反转数组中元素的顺序,会修改原数组,返回当前数组

shift()

移除数组中第一个元素

slice(start,end)

从start开始,到end为止(不包括end,可选,默认值是length)复制数组

sort(comparefn)

对数组中的内容排序,并不能给数字排序,因为默认比较函数是假定要被排序的元素都是字符串。

比较函数接受两个参数,并且如果两个参数相等返回0,如果第一个参数应该排在前面,则返回一个负数,如果第二个参数应该排在前面,则返回一个正数。

// a 比 b小时返回 -1,而且根据上述规则 a 会排在前面
// 所以这是从小到大的排序 var arr = [1,123,341,34,123]
arr.sort(function(a,b){
if(a==b){
return 0
}else{
return a < b ? -1 : 1
}
})

splice(start,deleteCount,item...)

splice 从数组中移除一个或多个元素,并用新的item代替他们。

start是从数组中移除元素的开始位置,deleteCount是要移除的个数。

会修改原数组,返回一个包含被移除元素的数组。

var arr = [23,3,23,2]
var b = arr.splice(0,1) console.log(arr) ; //[ 3, 23, 2 ]
console.log(b) ; //[ 23 ]

deleteCount 为0时,则为添加新元素:

var arr = [23,3,23,2]
var b = arr.splice(1,0,'aa') console.log(arr) // [ 23, 'aa', 3, 23, 2 ]
console.log(b) // []

deleteCount 与 item的个数相等时,则为替换:

var arr = [23,3,23,2]
var b = arr.splice(1,1,'aa') console.log(arr) //[ 23, 'aa', 23, 2 ]
console.log(b) //[ 3 ]

unshift(item...)

将item从数组头部插入数组

Function

apply(thisArg,argArray)

见 『Apply/Call调用模式』

Number

toFixed(fractionDigits)

把这个 number 转换成一个十进制形式的字符串。可选参数 fractionDigits 控制其小数点后的数字位数。

Math.PI.toFixed(); //
Math.PI.toFixed(2); //3.14
Math.PI.toFixed(4); //3.1415

toPrecision(precision)

同 toFixed ,参数控制有效数字的位数

toString()

将number转换成字符串

Object

hasOwnProperty(name)

只检查此对象中的属性,原型链中得同名属性不会被检查。如果存在此属性则返回 true

String

charAt(pos)

返回在字符串中pos处的字符

charCodeAt(pos)

返回不是一个字符串,而是以整数形式表示的字符码位

concat(string...)

与其他字符串连接起来构造一个新字符串,不常用,因为 + 也能满足需求

indexOf(searchString,pos)

在字符串内查找另一个字符串 searchString,如果被找到,则返回第一个匹配字符的位置,否则返回 -1 。

可选参数 pos 设置从字符串的某个指定位置开始查找。

lastIndexOf(searchString,pos)

与indexOf类似,不同从末尾开始查找

slice(start,end)

复制字符串的一部分构造一个新的字符串

split(separator,limit)

把字符串分割成片段创建数组,limit可限制被分割的片段数量。
一个有意思的技巧:

new Array(11).join('0').split('') //生成10个元素为0的数组

toLowerCase()

将字符串中所有字母转化为小写

toUpperCase()

将字符串中所有字母转化为大写

六、糟粕

这一部分用来吐槽JS这门语言设计上不周到的地方

全局变量

共三种方法定义全局变量:

  1. 脱离任何函数var语句 var foo = value
  2. 直接添加一个属性到全局对象中,全局对象是所有全局变量的容器。在web中,全局对象是 window : window.foo = value
  3. 使用未声明的变量,这被称为隐式的全局变量:foo = value

之前说过,可以通过 创建一个全局变量 和 闭包 减少全局变量污染(注意,只是减少,没办法避免,总要有暴露出来的变量,不要钻牛角尖)。

作用域

没有块级作用域,只有函数作用域

自动插入分号

JavaScript 有一个机制,会试图通过自动插入分号来修正有缺损的程序。它有可能会掩盖更为严重的错误。

return
{
status: true
}

看起来是返回一个对象,但是自动插入分号让它返回了undefined,这样可以避免:

return {
status: true
}

typeof

typeof并不能正确地检测数据类型:

typeof null ; //object

所以使用 Object.prototype.toString.call(null) 这个办法就好,万能的!

你不知道的javascript的更多相关文章

  1. 《你不知道的JavaScript》整理(二)——this

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,这次研究了一下“this”. 当一个函数被调用时,会创建一个活动记录(执行上下文). 这个记录会包含函 ...

  2. 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...

  3. 你不知道的Javascript(上卷)读书笔记之一 ---- 作用域

    你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此 ...

  4. 你不知道的JavaScript上卷笔记

    你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章   初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目 ...

  5. 【读书笔记】-- 你不知道的JavaScript

    <你不知道的JavaScript>是一个不错的JavaScript系列书,书名可能有些标题党的意思,但实符其名,很多地方会让你有耳目一新的感觉. 1.typeof null === &qu ...

  6. 读书笔记-你不知道的JavaScript&lpar;上&rpar;

    本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...

  7. 读《你不知道的JavaScript&lpar;上卷&rpar;》后感-作用域闭包(二)

    github原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们, ...

  8. 你不知道的javaScript上卷&lpar;第一章 作用域是什么&rpar;

    在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...

  9. JavaScript中的this(你不知道的JavaScript)

    JavaScript中的this,刚接触JavaScript时大家都在大肆渲染说其多么多么的灵巧重要,然而自己并不关心:随着自己对JavaScript一步步深入了解,突然恍然大悟,原来它真的很重要!所 ...

  10. 你不知道的Javascript:有趣的setTimeout

    你不知道的Javascript:有趣的setTimeout 有时候,小小的细节往往隐藏着大大的智慧今天在回顾JavaScript进阶用法的时候,发现一个有趣的问题,话不多说,先上代码: for(var ...

随机推荐

  1. HDU 5907 Find Q(简单字符串)

    传送门 Description Byteasar is addicted to the English letter 'q'. Now he comes across a string S consi ...

  2. Redis整合Spring结合使用缓存实例(三)

    一.Redis介绍 什么是Redis? redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set( ...

  3. &lbrack;WPF&rsqb;资源字典——程序集之间的资源共享 简单换皮肤

    直接上代码,已便已后自己查况阅,新手也可以看! 1.新建一个资料类和一个WPF工程 2.APP.XAML应该资源字典,注意应Source格式,前面一定要有“/” <ResourceDiction ...

  4. 十二 个经典 Linux 进程管理命令介绍

    执行中的程序在称作进程.当程序以可执行文件存放在存储中,并且运行的时候,每个进程会被动态得分配系统资源.内存.安全属性和与之相关的状态.可以有多个进程关联到同一个程序,并同时执行不会互相干扰.操作系统 ...

  5. JavaScript一看就懂&lpar;1&rpar;作用域

    函数级作用域 1.函数外声明的变量为全局变量,函数内可以直接访问全局变量: var global_var = 10; //全局变量 function a(){ alert(global_var); / ...

  6. &lbrack;Noi2016&rsqb;优秀的拆分

    来自F allDream的博客,未经允许,请勿转载,谢谢. 如果一个字符串可以被拆分为 AABB 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 aab ...

  7. 创建一个dynamics 365 CRM online plugin &lpar;九&rpar; - Context&period;Depth

    让我们来看看官方文档是怎么讲的 https://docs.microsoft.com/en-us/previous-versions/dynamicscrm-2016/developers-guide ...

  8. 从零开始学 Web 之 ES6(四)ES6基础语法二

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...

  9. 进程间通信IPC &lpar;InterProcess Communication&rpar;

    一.进程间通信的概念 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区, ...

  10. Windows控制程序网站带宽及Qos(TOS或DSCP)

    [基于策略的 Qos]位置:gpedit.msc->本地计算机策略->用户配置->Windows 设置->基于策略的 Qos