js语法补充
with
with语句可以形成自己的作用域
var obj = {
name: 'zsf'
}
with(obj) {
console.log(name)
}
现在已经不推荐了
eval
全局函数
var str = 'console.log(123)'
eval(str)//123
不建议:
- 可读性差
- 可能会被修改内容,被攻击
- 必须经过js解释器,不能js引擎优化,执行效率低
严格模式
es5提出
严格模式的限制:
- 通过抛出错误来消除一些原有的静默(silent)错误
- 让js引擎执行代码时可以进行更多的优化
- 禁用了ECMAScript未来版本可能会定义的语法
开启
- js文件
- 某个函数中
js文件
文件顶部写上 use strict
某个函数中
function () {
'use strict'
...
}
严格模式常见限制
- 意外创建全局变量
- 不允许函数有相同参数名称
- 静默错误
- 不允许使用原先的8进制格式
- 不允许with语句
- eval函数不会向上引用变量
- 独立函数(自执行)的this指向undefined
- setTimeout的this指向依然是window
面向对象
创建对象
创建对象方式:
- 字面量
- new Object
字面量
var obj = {
...
}
new
var obj = new Object()
obj.name = 'zsf'
创建多个对象的方案
工厂模式
function Person(name, age) {
var p = {}
p.name = name
p.age = age
p.run = function run() {
console.log('跑步')
}
return p
}
var p1 = new Person('zsf', 18)
var p2 = new Person('hhh', 19)
console.log(p1)
console.log(p2)
缺点:
打印对象时,都是Object类型,获取不到对象真实的类型
构造函数(构造器)
当使用new调用函数时,和普通调用函数有什么区别?
new调用函数时,发生了什么?
- 在内存中创建一个新对象(空)
- 将构造函数的显式原型prototype赋值给前面创建出来的对象的隐式原型proto
- 构造函数内部的this,会指向创建出来的新对象
- 执行函数代码
- 如果构造函数没有返回非空对象,这返回创建出来的新对象
用代码表示
function Person() {
}
var p = new Person()
内部原理
function Person() {
var moni = {}
this = moni
this.__proto__ = Person.prototype
执行代码
return moni
}
var p = new Person()
moni对象变成了 { __proto__: Person.prototype }
function Person(name, age) {
this.name = name
this.age = age
}
var p1 = new Person('zsf', 18)
console.log(p1)
弥补了工厂模式的不足
但是也有缺点
当对象内某个属性的值是函数的时候,相当于重复的创建了
原型和和构造函数结合
function Person(name, age) {
this.name = name
this.age = age
}
// 函数放原型
Person.prototype.eat = function () {
console.log(this.name + '吃东西')
}
var p1 = new Person('zsf', 18)
var p2 = new Person('hhh', 19)
p1.eat()
p2.eat()
对操作属性的控制
需求:对一个属性进行比较精准的操作控制
需要使用属性修饰符:
- 可以精准地添加或修改对象的属性
- 使用Object.definedProperty添加或修改属性
Object.definedProperty
Object.definedProperty()会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
可接收3个参数:
- obj要操作的对象
- prop要定义或修改的属性名称或Symbol
- description要定义或修改的属性描述符
返回值
传递给函数的对象
var obj = {
name: 'zsf',
age: 18
}
// 属性描述符是一个对象
Object.defineProperty(obj, 'height', {
value: 1.88
})
console.log(obj)
属性描述符
分2类:
- 数据属性描述符
- 存取属性描述符
Configurable
是否可以通过delete删除属性,是否可以修改它的属性,或者是否可以将它修改成存取属性,默认false
Enumerable
是否可以通过for-in或Object.keys返回该属性,默认false
Writable
是否可以修改属性的值,默认false
value
属性的值,默认undefined
var obj = {
name: 'zsf',
age: 18,
_address: '广州市'
}
// 存取属性描述符
Object.defineProperty(obj, 'address', {
enumerable: true,
configurable: true,
// 私有属性
get: function() {
return this._address
},
set: function(value) {
this._address = value
}
})
console.log(obj.address)
存取属性描述符应用场景:
- 隐藏私有属性不希望被外界使用和赋值
- 如果希望截获一个属性它访问和设置过程
var obj = {
name: 'zsf',
age: 18,
_address: '广州市'
}
// 存取属性描述符
Object.defineProperty(obj, 'address', {
enumerable: true,
configurable: true,
// 私有属性
get: function() {
foo()
return this._address
},
set: function(value) {
this._address = value
}
})
function foo() {
console.log('被使用了')
}
console.log(obj.address)
定义多个属性描述符
var obj = {
}
// 多个属性描述符
Object.defineProperties(obj, {
name: {
enumerable: true,
configurable: true,
value: 'zsf'
},
age: {
value: 19
}
})
console.log(obj)
对象的其它方法
- preventExtends(obj) 阻止拓展(添加属性)
- getOwnPropertyDescriptors 获取对象所有的属性的描述符
- seal 设置所有属性描述符为false
原型
js中每个对象都有一个__proto__属性(是个对象),这个对象叫对象的原型(隐式原型),这个对象还会指向另一个对象(对应的原型对象)
var obj = {
name: 'zsf',
__proro__: {}
}
对象原型(隐式原型)
隐式原型有什么用呢?
- 在当前对象中去查找对应的属性,如果找到就直接使用
- 如果没有找到,那么会沿着他的原型去查找
- 为什么要把有些属性放进原型,而不放在对象里?为了继承,这样就不用每个对象都放,而是需要就去原型里面找,这也就解决了构造函数可能重复创建某些可能不需要的属性了
var obj = {
name: 'zsf'
}
console.log(obj.age)// undefined
var obj = {
name: 'zsf'
}
obj.__proto__.age = 10
console.log(obj.age)// 10
函数原型(显式原型)
函数原型有什么用呢?
new调用函数时,发生了什么?
- 在内存中创建一个新对象(空)
- 将构造函数的显式原型prototype赋值给前面创建出来的对象的隐式原型proto
- 构造函数内部的this,会指向创建出来的新对象
- 执行函数代码
- 如果构造函数没有返回非空对象,这返回创建出来的新对象
第2步
function foo() {
// 下面三行代码是内部自动操作的,不用写
var moni = {}
this = {}
// 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
this.__proto__ = foo.prototype
}
console.log(foo.prototype)
new foo()
构造函数的prototype
构造函数的prototype属性指向了他的原型对象,该原型对象里面有constructor属性
prototype.constructor指向构造函数本身
可以往prototype里添加的属性吗?
可以的
而且还可以添加多个
function foo() {
}
foo.prototype = {
name: 'zsf',
age: 18
}
var f1 = new foo()
console.log(f1.name, f1.age);
但是一定要加constructor,原来prototype有,不添加会影响原型链
function foo() {
}
foo.prototype = {
constructor: foo,
name: 'zsf',
age: 18
}
var f1 = new foo()
console.log(f1.name, f1.age);
这样添加的constructor,可枚举为true,应该设置为false
开发中应该这样添加constructor(用对象的defineProperty)
function foo() {
}
foo.prototype = {
name: 'zsf',
age: 18
}
Object.defineProperty(foo.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: foo
})
var f1 = new foo()
console.log(f1.name, f1.age);
对象的其它方法、操作符
| hasOwnProperty() | 对象是否有某一个属于自己的属性(而不是原型上的属性) |
|---|---|
| in/for in 操作符 | 判断某个属性是否在对象上或对象的原型上 |
| instanceof | 检测构造函数的prototype,是否出现在某个实例对象的原型链上(stu instanceof Student) |
| isPrototypeOf() | 检测某个对象,是否出现在某个实例对象的原型链上 |
原型链
从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取
可观察到原型链的例子
var obj = {
name: 'zsf',
age: 18
}
obj.__proto__ = {
}
console.log(obj.address)// undefined
obj.__proto__.__proto__ = {
address: '广州'
}
console.log(obj.address)// 广州
Object的原型对象
原型链最顶层的原型对象--Object原型对象
原型链是有尽头的(Object.prototype)
var obj = {
name: 'zsf',
age: 18
}
console.log(obj.__proto__)// 尽头
console.log(obj.__proto__.__proto__)// null 因为没了
new Object() 发生了什么,类似于new调用构造函数
var obj1 = new Object()
Object()内部
var moni = {}
this = moni
this.__proto__ = Object.prototype
执行代码
return moni
moni对象变成了 { __proto__: Object.prototype }
返回的moni对象会被obj1接收
也就是
obj1.__proto__ = Object.prototype
Object本质也是构造函数
既然是构造函数,那就有显式原型prototype
继承
为什么需要继承?
当多个类有很多重复的代码,应该把公共的部分抽离出来封装成父类,子类继承
function Student(name, age) {
this.name = name
this.age = age
}
Student.prototype.run = function () {
console.log(this.name + '跑步')
}
function Teacher(name, age) {
this.name = name
this.age = age
}
Teacher.prototype.run = function () {
console.log(this.name + '跑步')
}
直接使用原型链继承
继承例子
function Person(name, age) {
this.name = 'zsf'
this.age = age
}
Person.prototype.run = function () {
console.log(this.name + '跑步')
}
function Teacher(name) {
this.age = 't'
}
var p = new Person()
Teacher.prototype = p
Teacher.prototype.eat = function () {
console.log(this.name + '吃')
}
var t1 = new Teacher()
console.log(t1.name)
t1.run()
console.log(p)
本来t1是没有name和run()的,加上
var p = new Person()
Teacher.prototype = p
之后,就有了
弊端
这种继承有3种弊端
- 打印t1对象,继承属性是看不到的
- 当修改父类中的属性时,多个子类都会受到影响
- 实现类的过程都没有传参数
借用构造函数继承
原理
为了能复用age,name这些属性,借用了构造函数Person(),把它当成普通函数去调用,通过call绑定this与Student,这样就是给Student添加age,name这些属性,而不是给Person添加
为了能复用running这些方法,new Person()时创建出来一个p对象 ,p对象的隐式原型__proto__又指向Person显式原型prototype,然后将Student的显式原型prototype指向p对象,这样就做到了Student的显式原型prototype指向Person显式原型prototype(但是不能直接Student的显式原型prototype指向Person显式原型prototype!!!)
这样,当stu.running()的时候就会沿着原型链找到这个方法,从而实现复用
如果直接 Student.prototype = Person.prototype
那当Student.prototype.studying = function(){}的时候,就是给Person添加studying方法了,显然,本来是给子类添加方法,现在把方法添加给了父类,这不符合面向对象的原则
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.run = function () {
console.log(this.name + '跑步')
}
function Teacher(name, age) {
Person.call(this, name, age)
this.tno = 102
}
var p = new Person()
Teacher.prototype = p
Teacher.prototype.eat = function () {
console.log(this.name + '吃')
}
var t1 = new Teacher('zsf', 18)
console.log(t1)
弊端
解决了原型链继承的弊端,但是这种也有2种弊端:
- 父类构造函数至少被调用2次
- t1原型对象(p)有多余的属性(子类已经有了)
寄生式继承
道格拉斯
原型式继承+工厂函数
var personObj = {
running: function () {
console.log('跑')
}
}
function createStudent(name) {
var stu = Object.create(personObj)
stu.name = name
stu.stufying = function () {
console.log('study')
}
return stu
}
var stuObj1 = createStudent('zsf')
var stuObj2 = createStudent('hhh')
这种方式有原型式继承和工厂函数的弊端
寄生组合式继承
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log('running')
}
Person.prototype.eating = function () {
console.log('eating')
}
// 创建出一个对象,并指向Person的显式原型(prototype),然后赋值给Student的prototype
Student.prototype = Object.create(Person.prototype)
// 修改准确描述符,不然打印出来的是Person类型,因为刚刚创建出来的对象没有constructor,所以会上去找Person的constructor,而constructor指向构造函数本身,因此是Person类
Object.defineProperty(Student.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: Student
})
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
Student.prototype.studying = function () {
console.log('studying')
}
这样不具有通用性
因此可以封装一个工具函数
// 当然,要是觉得Object.create太新,也可以自定义,换掉就行
function createObject(o) {
function fn() {}
fn.prototype = 0
return new fn()
}
// 封装成工具类函数
function inheritPrototype(SubType, SuperType) {
// 创建出一个对象,并指向Person的显式原型(prototype),然后赋值给Student的prototype
SubType.prototype = Object.create(SuperType.prototype)
// 修改准确描述符,不然打印出来的是Person类型,因为刚刚创建出来的对象没有constructor,所以会上去找Person的constructor,而constructor指向构造函数本身,因此是Person类
Object.defineProperty(SubType.prototype, 'constructor', {
enumerable: false,
configurable: true,
writable: true,
value: SubType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log('running')
}
Person.prototype.eating = function () {
console.log('eating')
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
// 寄生组合式继承
inheritPrototype(Student, Person)
Student.prototype.studying = function () {
console.log('studying')
}
原型继承关系
函数的特殊
- foo是一个函数,有个显示原型prototype
- foo也是一个对象,也会有一个隐式原型
__proto__ - 这两个原型不相等
- prototype来自哪里?创建了一个函数 foo.prototype = {constructor: foo }
__proto__来自哪里? new Function(), foo.__proto__= Function.prototype, 而 Function.prototype = { constructor: Function }- 特殊:Function.
__proto__= Function.prototype
function foo() {
}
// 等同于
var foo = new Function()