2 对象与函数
约 1145 个字 194 行代码 预计阅读时间 6 分钟
1 对象
- JS 中的「变量」被保存在 Stack 中,基本数据类型的值直接在栈内存中进行存储
JS 中的「对象」被保存在 Heap 中,通过保存内存地址空间建立对象名与数据的联系
可以通过设置
obj = null
断开对象名与数据的联系比较两个对象时,比较的是两个对象的「内存地址」(与数据是否一致无关)
-
对象的分类
-
内建对象(
Math
,Function
,Number
, ...)由 ES 标准定义的对象,在任何 ES 实现中均可以使用
-
宿主对象(
DOM
,BOM
)由 JS 运行环境(主要是浏览器)提供的对象
-
自定义对象
开发人员自行创建的对象
-
1.1 基本操作
属性名
-
对象的属性名「不强制要求」遵守标识符命名规范
比如属性
obj.var
就可以正常读写 -
若命名特殊属性名(比如纯数字),就需要用以下方式:
-
可以使用
in
运算符检验对象是否含有指定属性
对象中的属性值可以是任意类型,包括 Object。
-
创建对象
-
为对象添加属性
-
读取指定属性
-
修改指定属性
-
删除指定属性
js delete obj.keyName;
-
枚举对象中的属性
1.2 方法
使用 Function 作为属性值
1.3 使用「工厂方法」创建对象
返回的都是 Object(没有类型)
// 批量创建结构相似的 object
function createPerson(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
var p1 = createPerson("Tom", 17);
var p2 = createPerson("Tim", 22);
1.4 构造函数
- 首字母大写(不强制)
-
只能通过
new
关键字调用(不加会被当成普通函数)=> 立刻创建一个新的 Object,并设为函数的
this
,最后返回 -
使用同一个构造函数创建的对象被认为属于”同一类“ => 该类的实例
可以使用
obj instanceof className
判断实例是否属于某一类(返回 Boolean)=>
obj instanceof Object === true
(所有类都是 Object 的后代)
function Person(name, age) { // 可以认为定义了一个 class
// this 是新建的对象
this.name = name;
this.age = age;
// 每执行一次就会创建一个「新的·唯一的」sayName 方法 => 最好封装一下
this.sayName = function() {
console.log("Hello, I'm " + this.name);
}
// 改良后的表达 => 所有该类实例共用一个函数
this.sayName = fun;
}
// 在全局作用域中定义方法 => 污染了全局作用域的命名空间
function fun() {
console.log("Hello, I'm " + this.name);
}
var p = new Person("Tom", 19);
1.5 原型对象
同一类的实例均能访问原型对象,可以设置一些共有属性和方法
解析器会为每一个函数添加属性 prototype
,保存原型对象地址:
- 作为普通函数调用时,无意义
- 作为「构造函数」调用时,所创建实例中均存在指向该原型对象的隐含属性
__proto__
// 向原型对象添加共有属性 a
MyClass.prototype.a = 1;
var mc = new MyClass(); // mc 自身没有 a 属性
console.log(mc.a); // -> 去原型对象中找到了 a(mc有a时被实例属性覆盖)
console.log("a" in mc); // -> true 原型中有也会返回 true
console.log(ms.hasOwnProperty("a")); // -> false 对象自身不含 a 属性
// 向原型对象添加共有方法 sayHello
Myclass.prototypr.sayHello = function() {alert("hello")};
mc.sayHello(); // 调用原型中的 sayHello 方法(只有1个且不会污染全局作用域)
寻找属性/方法的顺序:
- 自身
- 原型对象
- 原型的原型对象
- 持续性套娃至 Object 的原型
- 返回 undefined
返回
null
是因为当前属性本身值为null
(不是不存在)
1.6 自定义 toString
当我们尝试打印对象时,实际上显示的是对象 toString
方法的返回结果
默认调用的是 Object 原型上的 toString
,但是很难看捏 => 自定义一下吧!
className.prototype.toString = function() {
// 一定要 return 一个 String 类型的结果捏
return "Person[name="+this.name+",age="+this.age+"]";
}
// 似乎 console.log 会打出 Object 结构
// 但 document.write 会显示 toString 结果
2 函数
函数也是一个对象
2.1 创建函数
-
调用构造函数
-
使用函数声明
-
使用函数表达式(匿名函数)
立即执行函数
仅执行一次的匿名函数(小括号包裹,并在末尾加()
)
2.2 参数与返回值
- 形参
- 多个形参之间使用
,
隔开 - 形参将在调用时被赋值(传少了就是
undefined
) - 解析器「不会」检查实参类型与数量(多的被丢弃)
- 多个形参之间使用
- 实参
- 可以是任意数据类型
- 实参为 Object
- 实参为 Function
- 可以是任意数据类型
- 返回值
- 需要手动
return 返回值
(可以丢个表达式返回执行结果),缺省时返回undefined
return
之后的语句会被跳过- 返回值可以为任意类型(可以是函数或对象捏)
- 需要手动
2.3 this 指针
解析器在每次调用函数时都会传入隐含参数
this
,指向函数执行的上下文对象
视「调用方式」不同,this
将会指向不同的对象:
- 直接通过函数名调用:返回
window
-
以「方法」形式调用:返回对应的
Object
function f() { console.log(this); } f(); // 输出 window var obj = { func: f }; obj.func(); // 输出 obj // 不太妙的例子 var name = "global"; function sayName() { console.log(name); // 此处一直用全局的 name console.log(this.name); // 正常了捏 } var obj = { sayName: sayName }; sayName(); // => global obj.sayName(); // => global
-
通过「函数对象调用
call / reply
」:返回指定对象call(neoThisObj, param1, param2, ...)
可以在对象后依次传递实参apply(neoThisObj, [param1, param2, ...])
需要将实参放进数组统一传递
function fun() { console.log(this); } // 不指定参数,this = window fun.call(), fun.apply(); // 指定参数,this = 传入对象 var obj = {}; fun.call(obj), fun.apply(obj); // 一个 NTR 的例子 var p1 = { name: 'p1', sayName: function(a, b) { console.log(this.name + a + b); } } var p2 = { name: 'p2' } p1.sayName("h1", "h2"); // => p1,h1,h2 // call & apply 的不同实参传递方式 p1.sayName.call(p2, "h1", "h2"); // => p2,h1,h2 p1.sayName.apply(p2,["h1", "h2"]); // => p2,h1,h2
-
以「构造函数」形式调用:返回新建对象
- 以「事件响应函数」形式调用:返回被绑定对象(触发对象)
- 在「事件绑定」中
addEventListener
的回调:返回绑定对象attachEvent
的回调:返回window
2.4 实参 arguments
在调用函数时,浏览器会传入两个隐含参数:
- 上下文对象
this
-
实参封装的类数组对象
arguments
- 可以通过下标操作数据,也可以获取长度(但不是数组实例)
- 可以通过
callee
属性返回当前正在执行的函数对象
function fun(a,b,c) { // 三个「形参」
console.log(arguments.length);
}
// arguments.length 和定义「形参」的数量无关
fun(1,2); // => 输出 2
fun(1,2,3,4,5); // => 输出 5
// 通过下标获取实参 arguments[idx] -> 不定义形参也能用
function f() {
console.log(arguments[0])
}
f(1); // 输出 1
// 检查 callee 是否为当前函数对象
function check() {
console.log(arguments.callee == check);
}
check(); // => 输出 true